haskell-notes

Два первых аргумента функции 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 и прибавляет к нему двойку. Мы видим, что этой функцией можно

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

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

*Nat> let add2 = (+) (Succ (Succ Zero))

*Nat> add2 Zero

Succ (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) 1

1

*Nat> (2) 1

< interactive>:4:2:

Структура функций | 47

No instance for (Num (a0 -> t0))

arising from a use of syntactic negation

Possible fix: add an instance declaration for (Num (a0 -> t0))

In the expression: — 2

In the expression: (2) 1

In an equation for ‘it’: it = (2) 1

Ох уж этот минус. Незадача. Ошибка произошла из-за того, что минус является хамелеоном. Если мы

пишем 2, компилятор воспринимает минус как унарную операцию, и думает, что мы написали константу

минус два. Это сделано для удобства, но иногда это мешает. Это единственное такое исключение в Haskell.

Давайте введём новый синоним для операции минус:

*Nat> let (#) = ()

*Nat> (2#) 1

1

*Nat> (#2) 1

1

Эти правила левого и правого применения работают и для буквенных имён в инфиксной форме записи:

*Nat> let minus = ()

*Nat> (2 ‘minus‘ ) 1

1

*Nat> ( ‘minus‘ 2) 1

1

Так если мы хотим на лету получить новую функцию, связав в функции второй аргумент мы можем

написать:

… = … ( ‘fun‘ x)

Частичное применение для функций в инфиксной форме записи называют сечением (section), они бывают

соответственно левыми и правыми.

Связь с логикой

Отметим связь основного правила применения с 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 -> String

show (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

(Succ (Succ (Succ (Succ (Succ (Succ (Succ Zero)))))))

= True

is7

_

= False

С помощью переменных мы даём синонимы поддеревьям. Этими синонимами мы можем пользоваться в

правой части функции. Так в уравнении

addZero (a:[])

мы извлекаем первый элемент из списка, и одновременно говорим о том, что список может содержать

Страницы: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162