64 | Глава 4: Декларативный и композиционный стиль
notT :: Bool -> Bool
notT True = False
Что мы увидим?
Prelude>notT False
*** Exception: <interactive >:1 :4 -20 : Non-exhaustive patterns infunction notT
Интерпретатор сообщил нам о том, что он не нашёл уравнения для переданного в функцию значения.
Безымянные функции
В композиционном стиле функции определяются по-другому. Это необычный метод, он пришёл в
Haskell из лямбда-исчисления. Функции строятся с помощью специальных конструкций, которые называ-
ются лямбда-функциями. По сути лямбда-функции являются безымянными функциями. Давайте посмотрим
на лямбда функцию, которая прибавляет к аргументу единицу:
\x ->x +1
Для того, чтобы превратить лямбда-функцию в обычную функцию мысленно замените знак \ на имя
noName, а стрелку на знак равно:
noName x =x +1
Мы получили обычную функцию Haskell, с такими мы уже много раз встречались. Зачем специальный
синтаксис для определения безымянных функций? Ведь можно определить её в виде уравнений. К тому же
кому могут понадобиться безымянные функции? Ведь смысл функции в том, чтобы выделить определённый
шаблон поведения и затем ссылаться на него по имени функции.
Смысл безымянной функции в том, что ею, также как и любым другим элементом композиционного
стиля, можно пользоваться в любой части обычных выражений. С её помощью мы можем создавать функции
“на лету”. Предположим, что мы хотим профильтровать список чисел, мы хотим выбрать из них лишь те, что
меньше 10, но больше 2, и к тому же они должны быть чётными. Мы можем написать:
f ::[ Int] ->[ Int]
f =filter p
wherep x =x >2 &&x <10 &&even x
При этом нам приходится давать какое-нибудь имя предикату, например 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 xs
wherep x = ...
последний аргумент xs.
К примеру вместо
Определение функций | 65
add a b =( +) a b
мы можем просто написать:
add =( +)
Такой стиль определения функций называют бесточечным (point-free).
Давайте выразим функцию filter с помощью лямбда-функций:
filter ::(a -> Bool) ->([a] ->[a])
filter =\p ->\xs -> casexs of
[]
-> []
(x :xs) -> letrest =filter p xs
in
if
p x
thenx :rest
elserest
Мы определили функцию filter пользуясь только элементами композиционного стиля. Обратите внима-
ние на скобки в объявлении типа функции. Я хотел напомнить вам о том, что все функции в Haskell являются
функциями одного аргумента. Это определение функции filter как нельзя лучше подчёркивает этот факт.
Мы говорим, что функция filter является функцией одного аргумента p в выражении \p ->, которая возвра-
щает также функцию одного аргумента. Мы выписываем это в явном виде в выражении \xs ->. Далее идёт
выражение, которое содержит определение функции.
Отметим, что лямбда функции могут принимать несколько аргументов, в предыдущем определении мы
Читать дальше