смотрим на определение:
fix f = let
x = f xin
x
Если вы запутались, то посмыслу это определение равносильно такому:
fix f =
f (fix f)Функция fix берёт функцию и начинает бесконечно нанизывать её саму на себя. Так мы получаем, что-то
вроде:
f (f (f (f (...
))))Зачем нам такая функция? Помните в самом конце четвёртой главы в упражнениях мы составляли бес-
конечные потоки. Мы делали это так:
data Stream
a = a :& Stream aconstStream ::
a -> Stream aconstStream a =
a :& constStream a82 | Глава 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 ZeroFalse
С помощью функции fix можно выразить любую рекурсивную функцию. Посмотрим как на примере
функции foldNat, у нас есть рекурсивное определение:
foldNat ::
a -> (a -> a) -> Nat -> afoldNat z
s
Zero
=
zfoldNat z
s
(Succ
n)=
s (foldNat z s n)Необходимо привести его к виду:
x =
f xСлева и справа мы видим повторяются выражения foldNat z s, обозначим их за x:
x :: Nat ->
ax Zero
=
zx (Succ
n)=
s (x n)Теперь перенесём первый аргумент в правую часть, сопоставление с образцом превратится в case
-выражение:
x :: Nat ->
ax =
\nat -> case nat ofZero
->
zSucc
n->
s (x n)В правой части вынесем x из выражения с помощью лямбда функции:
x :: Nat ->
ax =
(\t -> \nat -> case nat ofZero
->
zSucc
n->
s (t n)) xСмотрите мы обозначили вхождение x в выражении справа за t и создали лямбда-функцию с таким ар-
гументом. Так мы вынесли x из выражения.
Получилось, мы пришли к виду комбинатора неподвижной точки:
x :: Nat ->
ax =
f xwhere
f = \t -> \nat -> case nat ofZero
->
zSucc
n->
s (t n)Приведём в более человеческий вид:
foldNat ::
a -> (a -> a) -> (Nat -> a)foldNat z s =
fix fwhere
f t = \nat -> case nat ofZero
->
zSucc
n->
s (t n)Комбинатор неподвижной точки | 83
5.6 Краткое содержание
Основные функции высшего порядка
Мы познакомились с функциями из модуля Data.Function
. Их можно разбить на несколько типов:• Примитивные функции (генераторы функций).
id
=
\x -> xconst a =
\_ -> a• Функции, которые комбинируют функции или функции и значения:
f .
g=
\x -> f (g x)f $
x=
f x(.*.
) ‘on‘ f = \x y -> f x .*. f y• Преобразователи функций, принимают функцию и возвращают функцию:
flip f =
\x y -> f y x• Комбинатор неподвижной точки:
fix f = let
x = f xin
x
Приоритет инфиксных операций
Мы узнали о специальном синтаксисе для задания приоритета применения функций в инфиксной форме:
infixl
3 #infixr
6 ‘op‘Приоритет складывается из двух частей: старшинства (от 1 до 9) и ассоциативности (бывает левая и
правая). Старшинство определяет распределение скобок между разными функциями:
infixl
6 +infixl
7 *1 +
2 * 3 == 1 + (2 * 3)А ассоциативность – между одинаковыми:
infixl
6 +infixr
8 ^1 +
2 + 3 == (1 + 2) + 31 ^
2 ^ 3 ==1 ^
(2 ^ 3)Мы узнали, что функции ($
) и (. ) стоят на разных концах шкалы приоритетов функций и как этимпользоваться.
5.7 Упражнения
• Просмотрите написанные вами функции, или функции из примеров. Можно ли их переписать с по-
мощью основных функций высшего порядка? Если да, то перепишите. Попробуйте определить их в
бесточечном стиле.
• В прошлой главе у нас было упражнение о потоках. Сделайте поток экземпляром класса Num
. Для этогопоток должен содержать значения из класса Num
. Методы из класса Num применяются поэлементно. Таксложение двух потоков будет выглядеть так:
(a1 :&
a2 :& a3 :& ... ) + (b1 :& b2 :& b3) ====
(a1 +
b1 :& a2 + b2 :& a3 + b3 :& ... )84 | Глава 5: Функции высшего порядка
• Определите приоритет инфиксной операции (:&
)так чтобы вам было удобно использовать её в сочетании с арифметическими операциями.
• Рассмотрим такой тип:
data St
a b = St (a -> (b, St a b))Этот тип хранит функцию, которая позволяет преобразовывать потоки значений. Определите функцию
применения:
ap :: St
a b -> [a] -> [b]Она принимает ленту входящих значений и возвращает ленту выходов. Определите для этого
типа несоколько основных функций высшего порядка. Чтобы не возникало конфликта имён с
модулем Data.Function
мы не будем его импортировать. Вместо него мы импортируем модульControl.Category
. Он содержит класс:class Category
cat whereid
::
cat a a(.
) :: cat b c -> cat a b -> cat a c