не удаётся разбить на несколько составляющих. Если у вас появляется много таких функций, то это повод
задуматься о создании нового типа данных.
Например в качестве точки на плоскости можно использовать пару (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
False
С помощью функции fix можно выразить любую рекурсивную функцию. Посмотрим как на примере
функции foldNat, у нас есть рекурсивное определение:
foldNat :: a -> (a -> a) -> Nat -> a
foldNat z
s
Zero
= z
foldNat z
s
(Succ n)
= s (foldNat z s n)
Необходимо привести его к виду:
x = f x
Слева и справа мы видим повторяются выражения foldNat z s, обозначим их за x:
x :: Nat -> a
x Zero
= z
x (Succ n)
= s (x n)
Теперь перенесём первый аргумент в правую часть, сопоставление с образцом превратится в case-
выражение:
x :: Nat -> a
x = \nat -> case nat of
Zero
-> z
Succ n
-> s (x n)
В правой части вынесем x из выражения с помощью лямбда функции:
x :: Nat -> a
x = (\t -> \nat -> case nat of
Zero
-> z
Succ n
-> s (t n)) x
Смотрите мы обозначили вхождение x в выражении справа за t и создали лямбда-функцию с таким ар-
гументом. Так мы вынесли x из выражения.
Получилось, мы пришли к виду комбинатора неподвижной точки:
x :: Nat -> a
x = f x
where f = \t -> \nat -> case nat of
Zero
-> z
Succ n
-> s (t n)