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

lift2 f a b :: m (c -> d)

-- a’ == a, b’ == b, c’ == c -> d

У нас опять появился тип m (c -> d) и к нему нам нужно применить значение m c, чтобы получить m d.

Этим как раз и занимается функция $$. Итак итоговое определение примет вид:

lift3 :: Kleisli m => (a -> b -> c -> d) -> m a -> m b -> m c -> m d lift3 f a b c = lift2 f a b $$ c

Так мы можем определить любую функцию liftN через функции liftN-1 и $$.

Несколько полезных функций

Теперь мы умеем применять к специальным значениям произвольные обычные функции. Определим ещё

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

специальный список:

Применение функций | 95

import Prelude hiding (id, (>> ), pred, sequence)

sequence :: Kleisli m => [m a] -> m [a]

sequence = foldr (lift2 (:)) (idK [])

Мы “спрячем” из Prelude одноимённую функцию sequence. Посмотрим на примеры:

*Kleisli> sequence [Just 1, Just 2, Just 3]

Just [1,2,3]

*Kleisli> sequence [Just 1, Nothing, Just 3]

Nothing

Во второй команде вся функция вернула Nothing потому что при объединении списка встретилось зна-

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

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

не определён.

Посмотрим как работает эта функция на списках:

*Kleisli> sequence [[1,2,3], [11,22]]

[[1,11],[1,22],[2,11],[2,22],[3,11],[3,22]]

Она составляет список всех комбинаций элементов из всех подсписков.

С помощью этой функции мы можем определить функцию mapK. Эта функция является аналогом обычной

функции map, но она применяет специальную функцию к списку значений.

mapK :: Kleisli m => (a -> m b) -> [a] -> m [b]

mapK f = sequence . map f

6.4 Функторы и монады

В этой главе мы выписали вручную все определения для класса Kleisli. Мы сделали это потому, что на

самом деле в арсенале стандартных средств Haskell такого класса нет. Класс Kleisli строит замкнутый мир

специальных функций a -> m b. Его цель построить язык в языке и сделать программирование со специ-

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

исключительно в целях облегчения понимания этого мира. Впрочем никто не мешает нам определить этот

класс и пользоваться им в наших программах.

А пока посмотрим, что есть в Haskell и как это соотносится с тем, что мы уже увидели. С помощью класса

Kleisli

мы научились делать три различных операции применения:

Применение:

• обычных функций одного аргумента к специальным значениям (функция +$).

• обычных функций произвольного числа аргументов к специальным значениям (функции +$ и $$)

• специальных функций к специальным значениям (функция *$).

В Haskell для решения этих задач предназначены три отдельных класса. Это функторы, аппликативные

функторы и монады.

Функторы

Посмотрим на определение класса Functor:

class Functor f where

fmap :: (a -> b) -> f a -> f b

Тип метода fmap совпадает с типом для функции +$:

(+$) :: Kleisli m => (a -> b) -> m a -> m b

Нам только нужно заменить m на f и зависимость от Kleisli на зависимость от Functor:

Итак в Haskell у нас есть базовая операция fmap применения обычной функции к значению из мира спе-

циальных функций. В модуле Control.Applicative определён инфиксный синоним <$> для этой функции.

96 | Глава 6: Функторы и монады: теория

Аппликативные функторы

Посмотрим на определение класса Applicative:

class Functor f => Applicative f where

pure

:: a -> f a

(<*> )

:: f (a -> b) -> f a -> f b

Если присмотреться к типам методов этого класса, то мы заметим, что это наши старые знакомые idK и

$$. Если для данного типа f определён экземпляр класса Applicative, то из контекста следует, что для него

также определён и экземпляр класса Functor.

Значит у нас есть функции fmap (или lift1) и <*> (или $$). С их помощью мы можем составить функции