haskell-notes

module FunNat where

import Prelude(Show(.. ), Eq(.. ), Num(.. ), error)

instance Show (t -> a) where

show _ = error ”Sorry, no show. It’s just for Num”

instance Eq (t -> a) where

(==) _ _ = error ”Sorry, no Eq. It’s just for Num”

instance Num a => Num (t -> a) where

(+) = fun2 (+)

(*) = fun2 (*)

() = fun2 ()

abs

= fun1 abs

signum

= fun1 signum

fromInteger = const . fromInteger

fun1 :: (a -> b) -> ((t -> a) -> (t -> b))

fun1 = (. )

fun2 :: (a -> b -> c) -> ((t -> a) -> (t -> b) -> (t -> c))

fun2 op a b = t -> a t ‘op‘ b t

Функции fun1 и fun2 превращают функции, которые принимают значения, в функции, которые прини-

мают другие функции.

Из-за контекста класса Num нам пришлось объявить два фиктивных экземпляра для классов Show и Eq.

Загрузим модуль FunNat в интерпретатор и посмотрим что же у нас получилось:

Prelude> :l FunNat. hs

[1 of 1] Compiling FunNat

( FunNat. hs, interpreted )

Ok, modules loaded: FunNat.

*FunNat> 2 2

2

*FunNat> 2 5

2

*FunNat> (2 + (+1)) 0

3

*FunNat> ((+2) * (+3)) 1

12

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

значение к константе. Но на самом деле 2 это не константа, а значение 2 :: Num a => a и подспудно к двойке

применяется функция fromInteger. Поскольку в нашем модуле мы определили экземпляр Num для функций,

второе число 2 было конкретизировано по умолчанию до Integer, а первое число 2 было конкретизировано

до Integer -> Integer. Компилятор вывел из контекста, что под 2 мы понимаем функцию. Функция была

создана с помощью метода fromInteger. Эта функция принимает любое значение и возвращает двойку.

Далее мы складываем и перемножаем функции словно это обычные значения. Что интересно мы можем

составлять и такие выражения:

*FunNat> let f = ((+) (*))

*FunNat> f 1 2

1

Как была вычислена эта функция? Мы определили экземпляр функций для значений типа Num a => t

-> a. Если мы вспомним, что функция двух аргументов на самом деле является функцией одного аргумента:

Num a => t1 -> (t2 -> a), мы заметим, что тип Num a => (t2 -> a) принадлежит Num, теперь если мы

обозначим его за a’, то мы получим тип Num a’ => t1 -> a’, это совпадает с нашим исходным экземпляром.

Получается, что за счёт механизма частичного применения мы одним махом определили экземпляры Num

для функций любого числа аргументов, которые возвращают значение типа Num.

Итак функция f имеет вид:

t1 t2 -> (t1 + t2) (t1 * t2)

Подставим значения:

Функциональный калькулятор | 79

(t1 t2 -> (t1 + t2) (t1 * t2)) 1 2

(t2 -> (1 + t2) (1 * t2) 2

(1 + 2) (1 * 2)

3 2

1

Теперь давайте составим несколько выражений с обобщёнными функциями. Для этого добавим в модуль

FunNat директиву импорта функций из модуля Data.Function. Также добавим несколько основных функций

для списков и класс Ord:

module FunNat where

import Prelude(Show(.. ), Eq(.. ), Ord(.. ), Num(.. ), error)

import Data.Function(id, const, (. ), ($), flip, on)

import Prelude(map, foldr, filter, zip, zipWith)

и загрузим модуль в интерпретатор:

Prelude> :load FunNat

[1 of 1] Compiling FunNat

( FunNat. hs, interpreted )

Ok, modules loaded: FunNat.

Составим функцию, которая принимает один аргумент, умножает его на два, вычитает 10 и берёт модуль

числа.

*FunNat> let f = abs $ id * 2 10

*FunNat> f 2

6

*FunNat> f 10

10

Давайте посмотрим как была составлена эта функция:

abs $ id * 2 10

=>

abs $ (id * 2) 10

— приоритет умножения

=>

abs $ (x -> x * x -> 2) 10

— развернём id и 2

=>

abs $ (x -> x * 2) 10

— по определению (*) для функций

=>

abs $ (x -> x * 2) x -> 10

— развернём 10

=>

abs $ x -> (x * 2) 10

— по определению (-) для функций

=>

x -> abs x . x -> (x * 2) 10

— по определению abs для функций

=>

x -> abs ((x * 2) 10)

— по определению (.)

=>

x -> abs ((x * 2) 10)

Функция возведения в квадрат:

*FunNat> let f = id * id

*FunNat> map f [1,2,3,4,5]

[1,4,9,16,25]

*FunNat> map (id * id 1) [1,2,3,4,5]

[0,3,8,15,24]

Обратите внимание на краткость записи. В этом выражении (id * id 1) проявляется основное пре-

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

это простые значения. Этот приём используется в Haskell очень активно. Пока нам встретились лишь две

инфиксных операции для функций (это композиция и применение с низким приоритетом), но в будущем вы

столкнётесь с целым морем подобных операций. Все они служат одной цели, они прячут аргументы функции,

позволяя быстро составлять функции на лету из примитивов. Чтобы не захлебнуться в этом море помните,

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

вида.

Возведём в четвёртую степень:

80 | Глава 5: Функции высшего порядка

*FunNat> map (f . f) [1,2,3,4,5]

[1,16,81,256,625]

Составим функцию двух аргументов, которая будет вычислять сумму квадратов двух аргументов:

*FunNat> let x = const id

*FunNat> let y = flip $ const id

*FunNat> let d = x * x + y * y

*FunNat> d 1 2

5

*FunNat> d 3 2

Страницы: 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