При этом нам приходится давать какое-нибудь имя предикату, например p. С помощью безымянной функ-
ции мы могли бы написать так:
f ::
[Int] -> [Int]f =
filter (\x -> x > 2 && x < 10 && even x)Смотрите мы составили предикат сразу в аргументе функции filter. Выражение (\x ->
x > 2 && x <10 &&
even x) является обычным значением.Возможно у вас появился вопрос, где аргумент функции? Где тот список по которому мы проводим филь-
трацию. Ответ на этот вопрос кроется в частичном применении. Давайте вычислим по правилу применения
тип функции filter:
f ::
(a -> Bool) -> [a] -> [a],x ::
(Int -> Bool)------------------------------------------------------
(f x) ::
[Int] -> [Int]После применения параметр a связывается с типом Int
, поскольку при применении происходит сопостав-ление более общего предиката a -> Bool
из функции filter с тем, который мы передали первым аргументомInt -> Bool
. После этого мы получаем тип (f x) :: [Int] -> [Int] это как раз тип функции, которая прини-мает список целых чисел и возвращает список целых чисел. Частичное применение позволяет нам не писать
в таких выражениях:
f xs =
filter p xswhere
p x = ...последний аргумент xs.
К примеру вместо
Определение функций | 65
add a b =
(+) a bмы можем просто написать:
add =
(+)Такой стиль определения функций называют
Давайте выразим функцию filter с помощью лямбда-функций:
filter ::
(a -> Bool) -> ([a] -> [a])filter =
\p -> \xs -> case xs of[]
-> []
(x:
xs) -> let rest = filter p xsin
if
p x
then
x : restelse
restМы определили функцию filter пользуясь только элементами композиционного стиля. Обратите внима-
ние на скобки в объявлении типа функции. Я хотел напомнить вам о том, что все функции в Haskell являются
функциями одного аргумента. Это определение функции filter как нельзя лучше подчёркивает этот факт.
Мы говорим, что функция filter является функцией одного аргумента p в выражении \p ->
, которая возвра-щает также функцию одного аргумента. Мы выписываем это в явном виде в выражении \xs ->
. Далее идётвыражение, которое содержит определение функции.
Отметим, что лямбда функции могут принимать несколько аргументов, в предыдущем определении мы
могли бы написать:
filter ::
(a -> Bool) -> ([a] -> [a])filter =
\p xs -> case xs of...
но это лишь синтаксический сахар, который разворачивается в предыдущую запись.
Для тренировки определим несколько стандартных функций для работы с кортежами с помощью лямбда-
функций (все они определены в Prelude
):fst ::
(a, b) -> afst =
\(a, _) -> asnd ::
(a, b) -> bsnd =
\(_, b) -> bswap ::
(a, b) -> (b, a)swap =
\(a, b) -> (b, a)Обратите внимание на то, что все функции словно являются константами. Они не содержат аргументов.
Аргументы мы “пристраиваем” с помощью безымянных функций.
Определим функции преобразования первого и второго элемента кортежа (эти функции определены в
модуле Control.Arrow
)first ::
(a -> a’) -> (a, b) -> (a’, b)first =
\f (a, b) -> (f a, b)second ::
(b -> b’) -> (a, b) -> (a, b’)second =
\f (a, b) -> (a, f b)Также в Prelude
есть полезные функции, которые превращают функции с частичным применением вобычны функции и наоборот:
curry ::
((a, b) -> c) -> a -> b -> ccurry =
\f -> \a -> \b -> f (a, b)uncurry ::
(a -> b -> c) -> ((a, b) -> c)uncurry =
\f -> \(a, b) -> f a b66 | Глава 4: Декларативный и композиционный стиль
Функция curry принимает функцию двух аргументов для которой частичное применение невозможно.
Это имитируется с помощью кортежей. Функция принимает кортеж из двух элементов. Функция curry (от
слова каррирование, частичное применение) превращает такую функцию в обычную функцию Haskell. А
функция uncurry выполняет обратное преобразование.
С помощью лямбда-функций можно имитировать локальные переменные. Так например можно перепи-
сать формулу для вычисления площади треугольника:
square a b c =
(\p ->
sqrt (p * (p - a) * (p - b) * (p - c)))((a +
b + c) / 2)Смотрите мы определили функцию, которая принимает параметром полупериметр p и передали в неё
значение ((a +
b + c) / 2). Если в нашей функции несколько локальных переменных, то мы можемсоставить лямбда-функцию от нескольких переменных и подставить в неё нужные значения.
4.5 Какой стиль лучше?
Основной критерий выбора заключается в том, сделает ли этот элемент код более
кода станет залогом успешной поддержки. Его будет легче понять и улучшить при необходимости.
Далее мы рассмотрим несколько примеров определений из Prelude
и подумаем, почему был выбран тотили иной стиль. Начнём с класса Ord
и посмотрим на определения по умолчанию:-- Тип упорядочивания
data
Ordering
=
LT | EQ | GT
deriving
(Eq, Ord, Enum, Read, Show, Bounded)class
(Eq
a) => Ord awhere
compare
::
a -> a -> Ordering(<
), (<=), (>=), (> ) :: a -> a -> Boolmax, min
::
a -> a -> a-- Минимальное полное определение:
--
(<=) или compare