Выбрать главу

не удаётся разбить на несколько составляющих. Если у вас появляется много таких функций, то это повод

задуматься о создании нового типа данных.

Например в качестве точки на плоскости можно использовать пару (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)