Давайте потренируемся с частичным применением в интерпретаторе. Для этого загрузим модуль 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 addTwoaddTwo :: 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
и прибавляет к нему двойку. Мы видим, что этой функцией можнопользоваться также как и обычной функцией.
Попробуем выполнить тоже самое для функции с символьной записью имени:
*Nat> let
add2 = (+) (Succ (Succ Zero))*Nat>
add2 ZeroSucc
(Succ Zero)Мы рассмотрели частичное применение для функций в префиксной форме записи. В префиксной фор-
ме записи функция пишется первой, затем следуют аргументы. Для функций в инфиксной форме записи
существует два правила применения.
Это применение слева:
(*
) :: a -> (b -> c),x ::
a-----------------------------
(x *
) :: b -> cИ применение справа:
(*
) :: a -> (b -> c),x ::
b-----------------------------
(*
x) :: a -> cОбратите внимание на типы аргумента и возвращаемого значения. Скобки в выражениях (x*
) и (*x)обязательны. Применением слева мы фиксируем в бинарной операции первый аргумент, а применением
справа – второй.
Поясним на примере, для этого давайте возьмём функцию минус (-
). Если мы напишем (2-) 1 то мыполучим 1, а если мы напишем (-
2) 1, то мы получим -1. Проверим в интерпретаторе:*Nat>
(2-) 11
*Nat>
(-2) 1<
interactive>:4:2:Структура функций | 47
No instance
for (Num (a0 -> t0))arising from a use of
syntactic negationPossible
fix: add an instance declaration for (Num (a0 -> t0))In
the expression: - 2In
the expression: (- 2) 1In
an equation for ‘it’: it = (- 2) 1Ох уж этот минус. Незадача. Ошибка произошла из-за того, что минус является хамелеоном. Если мы
пишем -
2, компилятор воспринимает минус как унарную операцию, и думает, что мы написали константуминус два. Это сделано для удобства, но иногда это мешает. Это единственное такое исключение в Haskell.
Давайте введём новый синоним для операции минус:
*Nat> let
(#) = (-)*Nat>
(2#) 11
*Nat>
(#2) 1-
1Эти правила левого и правого применения работают и для буквенных имён в инфиксной форме записи:
*Nat> let
minus = (-)*Nat>
(2 ‘minus‘ ) 11
*Nat>
( ‘minus‘ 2) 1-
1Так если мы хотим на лету получить новую функцию, связав в функции второй аргумент мы можем
написать:
... = ...
( ‘fun‘ x) ...Частичное применение для функций в инфиксной форме записи называют
соответственно левыми и правыми.
Связь с логикой
Отметим связь основного правила применения с Modus Ponens, известным правилом вывода в логике:
a ->
b,a
-------------
b
Оно говорит о том, что если у нас есть выражение из a следует b и мы знаем, что a истинно, мы смело
можем утверждать, что b тоже истинно. Если перевести это правило на Haskell, то мы получим: Если у нас
определена функция типа a ->
b и у нас есть значение типа a, то мы можем получить значение типа b.Декомпозиция и сопоставление с образцом
Декомпозиция применяется слева от знака равно, при этом наша задача состоит в том, чтобы опознать
дерево определённого вида и выделить из него некоторые поддеревья. Мы уже пользовались декомпозицией
много раз в предыдущих главах, давайте выпишем примеры декомпозиции:
not :: Bool -> Bool
not True
= ...
not False
= ...
xor :: Bool -> Bool -> Bool
xor a b = ...
show :: Show
a => a -> Stringshow (Time
h m s) = ...addZero :: String -> String
addZero (a:[]
)= ...
addZero as
= ...
(*
)a
Zero
= ...
(*
)a
(Succ
b)= ...
48 | Глава 3: Типы
Декомпозицию можно проводить в аргументах функции. Там мы видим строчную запись дерева, в узлах
стоят конструкторы (начинаются с большой буквы), переменные (с маленькой буквы) или символ безразлич-
ной переменой (подчёркивание).
С помощью конструкторов, мы указываем те части, которые обязательно должны быть в дереве для дан-
ного уравнения. Так уравнение
not True
= ...
сработает, только если на вход функции поступит значение True
. Мы можем углубляться в дерево значе-ния настолько, насколько нам позволят типы, так мы можем определить функцию:
is7 :: Nat -> Bool
is7