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

применением или аппликацией.

Выражения, полученные таким образом, напоминают строчную запись дерева, но есть одна тонкость, ко-

торую мы обошли стороной. В случае деревьев мы строили только константы, и конструктор получал столько

аргументов, сколько у него было дочерних узлов (или подтипов). Так мы строили константы. Но в Haskell мы

можем с помощью применения строить функции на лету, передавая меньшее число аргументов, этот процесс

называется частичным применением или каррированием (currying). Поясним на примере, предположим у нас

есть функция двух аргументов:

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) d

46 | Глава 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.

Давайте потренируемся с частичным применением в интерпретаторе. Для этого загрузим модуль Nat из

предыдущей главы:

Prelude> :l Nat

[1 of 1] Compiling Nat

( Nat. hs, interpreted )

Ok, modules loaded: Nat.

*Nat> let add = (+) :: Nat -> Nat -> Nat

*Nat> let addTwo = add (Succ (Succ Zero))

*Nat> :t addTwo

addTwo :: Nat -> Nat

*Nat> addTwo (Succ Zero)

Succ (Succ (Succ Zero))

*Nat> addTwo (addTwo Zero)

Succ (Succ (Succ (Succ Zero)))

Сначала мы ввели локальную переменную add, и присвоили ей метод (+) из класса Num для Nat. Нам

пришлось выписать тип функции, поскольку ghci не знает для какого экземпляра мы хотим определить этот

синоним. В данном случае мы подсказали ему, что это Nat. Затем с помощью частичного применения мы

объявили новый синоним addTwo, как мы видим из следующей строки это функция оного аргумента. Она

принимает любое значение типа Nat и прибавляет к нему двойку. Мы видим, что этой функцией можно

пользоваться также как и обычной функцией.

Попробуем выполнить тоже самое для функции с символьной записью имени: