для списков и класс Ord:
module FunNat where
import Prelude(Show(.. ), Eq(.. ), Ord(.. ), Num(.. ), error)
import Data.Function(id, const, (. ), ($), flip, on)
import Prelude(map, foldr, filter, zip, zipWith)
…
и загрузим модуль в интерпретатор:
Prelude> :load FunNat
[1 of 1] Compiling FunNat
( FunNat. hs, interpreted )
Ok, modules loaded: FunNat.
Составим функцию, которая принимает один аргумент, умножает его на два, вычитает 10 и берёт модуль
числа.
*FunNat> let f = abs $ id * 2 — 10
*FunNat> f 2
6
*FunNat> f 10
10
Давайте посмотрим как была составлена эта функция:
abs $ id * 2 — 10
=>
abs $ (id * 2) — 10
— приоритет умножения
=>
abs $ (x -> x * x -> 2) — 10
— развернём id и 2
=>
abs $ (x -> x * 2) — 10
— по определению (*) для функций
=>
abs $ (x -> x * 2) — x -> 10
— развернём 10
=>
abs $ x -> (x * 2) — 10
— по определению (-) для функций
=>
x -> abs x . x -> (x * 2) — 10
— по определению abs для функций
=>
x -> abs ((x * 2) — 10)
— по определению (.)
=>
x -> abs ((x * 2) — 10)
Функция возведения в квадрат:
*FunNat> let f = id * id
*FunNat> map f [1,2,3,4,5]
[1,4,9,16,25]
*FunNat> map (id * id — 1) [1,2,3,4,5]
[0,3,8,15,24]
Обратите внимание на краткость записи. В этом выражении (id * id — 1) проявляется основное пре-
имущество бесточечного стиля, избавившись от аргументов, мы можем пользоваться функциями так, словно
это простые значения. Этот приём используется в Haskell очень активно. Пока нам встретились лишь две
инфиксных операции для функций (это композиция и применение с низким приоритетом), но в будущем вы
столкнётесь с целым морем подобных операций. Все они служат одной цели, они прячут аргументы функции,
позволяя быстро составлять функции на лету из примитивов. Чтобы не захлебнуться в этом море помните,
что скорее всего новый символ означает либо композицию либо применение для функций специального
вида.
Возведём в четвёртую степень:
80 | Глава 5: Функции высшего порядка
*FunNat> map (f . f) [1,2,3,4,5]
[1,16,81,256,625]
Составим функцию двух аргументов, которая будет вычислять сумму квадратов двух аргументов:
*FunNat> let x = const id
*FunNat> let y = flip $ const id
*FunNat> let d = x * x + y * y
*FunNat> d 1 2
5
*FunNat> d 3 2
13
Так мы составили функцию, ни прибегая к помощи аргументов. Эти выражения могут стать частью других
выражений:
*FunNat> filter
((< 10) . d 1) [1,2,3,4,5]
[1,2]
*FunNat> zipWith d [1,2,3] [3,2,1]
[10,8,10]
*FunNat> foldr (x*x — y*y) 0 [1,2,3,4]
3721610024
*FunNat> zipWith ((—) * (—) + const id) [1,2,3] [3,2,1]
[7,2,5]
В последнем выражении трудно предугадать результат. В таких выражениях всё-таки лучше пользоваться
синонимами. В бесточечном стиле мы можем несколькими операциями собрать из базовых функций сложную
функцию и передать её аргументом в другую функцию, которая также может поучаствовать в комбинации
других функций!
5.4 Функции, возвращающие несколько значений
Как было сказано ранее функции, которые возвращают несколько значений, реализованы в Haskell с по-
мощью кортежей. Например функция, которая расщепляет поток на голову и хвост выглядит так:
decons :: Stream a -> (a, Stream a)
decons (a :& as) = (a, as)
Здесь функция возвращает сразу два значения. Но всегда ли уместно пользоваться кортежами? Для ком-
позиции функций, которые возвращают несколько значений нам придётся разбирать возвращаемые значения
с помощью сопоставления с образцом и затем использовать эти значения в других функциях. Посудите сами
если у нас есть функции:
f :: a
-> (b1, b2)
g :: b1 -> (c1, c2)
h :: b2 -> (c3, c4)
Мы уже не сможем комбинировать их так просто как если бы это были обычные функции без кортежей.
q x = ((a, b) -> (g a, h b)) (f x)
В случае пар нам могут прийти на помощь функции first и second:
q = first g . second h . f
Если мы захотим составить какую-нибудь другую функцию из q, то ситуация заметно усложнится. Функ-
ции, возвращающие кортежи, сложнее комбинировать в бесточечном стиле. Здесь стоит вспомнить правило
Unix.
Пишите функции, которые делают одну вещь, но делают её хорошо.
Функции, возвращающие несколько значений | 81
Функция, которая возвращает кортеж пытается сделать сразу несколько дел. И теряет в гибкости, ей
трудно взаимодействовать с другими функциями. Старайтесь чтобы таких функций было как можно меньше.
Если функция возвращает несколько значений, попытайтесь разбить её на несколько, которые возвраща-
ют лишь одно значение. Часто бывает так, что эти значения тесно связаны между собой и такую функцию
не удаётся разбить на несколько составляющих. Если у вас появляется много таких функций, то это повод
задуматься о создании нового типа данных.
Например в качестве точки на плоскости можно использовать пару (Float, Float). В этом случае, если
вы начнёте писать модуль на геометрическую тему у вас появится много функций, которые принимают и
возвращают точки:
rotate
:: Float -> (Float, Float) -> (Float, Float)
norm
:: (Float, Float) -> (Float, Float)
translate
:: (Float, Float) -> (Float, Float) -> (Float, Float)
…
Все они стараются делать несколько дел одновременно, возвращая кортежи. Но мы можем изменить
ситуацию определением новых типов:
data Point
= Point
Float Float
data Vector = Vector Float Float
data Angle
= Angle
Float
Объявления функций станут более краткими и наглядными.
rotate
:: Angle
-> Point -> Point
norm
:: Point
-> Point
translate
:: Vector -> Point -> Point
…
5.5 Комбинатор неподвижной точки
Познакомимся с функцией fix или комбинатором неподвижной точки. По хорошему об этой функции
следовало бы рассказать в разделе обобщённые функции. Но я пропустил её нарошно, для простоты изло-
жения. В этом разделе градус сложности резко подскакивает, если вы ранее не встречались с этой функцией
она может показаться вам очень необычной. Для начала посмотрим на её тип:
Prelude> :m +Data.Function
Prelude Data.Function> :t fix
fix :: (a -> a) -> a
Странно fix принимает функцию и возвращает значение, обычно всё происходит наоборот. Теперь по-
смотрим на определение:
fix f = let x = f x
in
x
Если вы запутались, то посмыслу это определение равносильно такому:
fix f = f (fix f)
Функция fix берёт функцию и начинает бесконечно нанизывать её саму на себя. Так мы получаем, что-то
вроде:
f (f (f (f (… ))))
Зачем нам такая функция? Помните в самом конце четвёртой главы в упражнениях мы составляли бес-
конечные потоки. Мы делали это так:
data Stream a = a :& Stream a
constStream :: a -> Stream a
constStream a = a :& constStream a
82 | Глава 5: Функции высшего порядка
Если смотреть на функцию constStream очень долго, то рано или поздно в ней проглянет функция fix. Я
нарошно не буду выписывать, а вы мысленно обозначьте (a :& ) за f и constStream a за fix f. Получилось?
Через fix можно очень просто определить бесконечность для Nat, бесконечность это цепочка Succ, ко-
торая никогда не заканчивается Zero. Оказывается, что в Haskell мы можем составлять выражения с такими
значениями (как это получается мы обудим попозже):
ghci Nat
*Nat> m + Data.Function
*Nat Data.Function> let infinity = fix Succ
*Nat Data.Function> infinity < Succ Zero





