станты. Теперь мы разобрались с тем, что константы это деревья. Значит функции строят одни деревья из
других. Как они это делают? Для этого этого в Haskell есть две операции: это композиция и декомпозиция де-
ревьев. С помощью
составные деревья на простейшие.
Композиция и декомпозиция объединены в одной операции, с которой мы уже встречались, это операция
определения синонима. Давайте вспомним какое-нибудь объявление функции:
(+
) aZero
=
a(+
) a(Succ
b)= Succ
(a + b)Смотрите в этой функции слева от знака равно мы проводим декомпозицию второго аргумента, а в правой
части мы составляем новое дерево из тех значений, что были нами получены слева от знака равно. Или
посмотрим на другой пример:
show (Time
h m s) = show h ++ ”:” ++ show m ++ ”:” ++ show sСлева от знака равно мы также выделили из составного дерева (Time
h m s) три его дочерних для корняузла и связали их с переменными h, m и s. А справа от знака равно мы составили из этих переменных новое
выражение.
Итак операцию объявления синонима можно представить в таком виде:
name
декомпозиция
=
композиция
В каждом уравнении у нас три части: новое имя, декомпозиция, поступающих на вход аргументов, и
композиция нового значения. Теперь давайте остановимся поподробнее на каждой из этих операций.
Структура функций | 45
Композиция и частичное применение
Композиция строится по очень простому правилу, если у нас есть значение f типа a ->
b и значение xтипа a, мы можем получить новое значение (f x) типа b. Это основное правило построения новых значений,
поэтому давайте запишем его отдельно:
f ::
a -> b,x ::
a--------------------------
(f x) ::
bСверху от черты, то что у нас есть, а снизу от черты то, что мы можем получить. Это операция называется
Выражения, полученные таким образом, напоминают строчную запись дерева, но есть одна тонкость, ко-
торую мы обошли стороной. В случае деревьев мы строили только константы, и конструктор получал столько
аргументов, сколько у него было дочерних узлов (или подтипов). Так мы строили константы. Но в Haskell мы
можем с помощью применения строить функции на лету, передавая меньшее число аргументов, этот процесс
называется
есть функция двух аргументов:
add :: Nat -> Nat -> Nat
add a b = ...
На самом деле компилятор воспринимает эту запись так:
add :: Nat ->
(Nat -> Nat)add a b = ...
Функция add является функцией одного аргумента, которая в свою очередь возвращает функцию одного
аргумента (Nat -> Nat
). Когда мы пишем в где-нибудь в правой части функции:... =
...
(add Zero (Succ Zero)) ...Компилятор воспринимает эту запись так:
... =
...
((add Zero) (Succ Zero)) ...Присмотримся к этому выражению, что изменилось? У нас появились новые скобки, вокруг выражения
(add Zero
). Давайте посмотрим как происходит применение:add :: Nat ->
(Nat -> Nat),Zero :: Nat
----------------------------------------------
(add Zero
) :: Nat -> NatИтак применение функции add к Zero
возвращает новую функцию (add Zero), которая зависит от одногоаргумента. Теперь применим к этой функции второе значение:
(add Zero
) :: Nat -> Nat,(Succ Zero
) :: Nat----------------------------------------------
((add Zero
) (Succ Zero)) :: NatИ только теперь мы получили константу. Обратите внимание на то, что получившаяся константа не может
принять ещё один аргумент. Поскольку в правиле для применения функция f
у нас есть лишь Nat
, это значение может участвовать в других выражениях лишь на месте аргумента.Тоже самое работает и для функций от большего числа аргументов, если мы пишем
fun ::
a1 -> a2 -> a3 -> a4 -> res... =
fun a b c dНа самом деле мы пишем
fun ::
a1 -> (a2 -> (a3 -> (a4 -> res)))... =
(((fun a) b) c) d46 | Глава 3: Типы
Это очень удобно. Так, определив лишь одну функцию fun, мы получили в подарок ещё три функции
(fun a), (fun a b) и (fun a b c). С ростом числа аргументов растёт и число подарков. Если смотреть на
функцию fun, как на функцию одного аргумента, то она представляется таким генератором функций типа
a2 ->
a3 -> a4 -> res, который зависит от параметра. Применение функций через пробел значительноупрощает процесс комбинирования функций.
Поэтому в Haskell аргументы функций, которые играют роль параметров или специфических флагов, то
есть аргументы, которые меняются редко обычно пишутся в начале функции. Например
process :: Param1 -> Param2 -> Arg1 -> Arg2 -> Result
Два первых аргумента функции process выступают в роли параметров для генерации функций с типом
Arg1 -> Arg2 -> Result
.