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