Раздел 21 · Haskell

Applicative: обёрнутая функция и валидация

middle-senior~40 мин

без входаВойди, чтобы отмечать прогресс, вести заметки и решать задачи в редакторе. войти

Applicative: обёрнутая функция и валидация

Третья ступень. Functor умел применить чистую функцию внутри структуры, но спотыкался на двух аргументах. Applicative решает это: он применяет обёрнутую функцию к обёрнутому значению через <*> и вводит pure, способ положить значение в структуру. В этом уроке: pure и <*>, законы, liftA2, разница между аппликативной и монадической валидацией (копим все ошибки против падения на первой), и Alternative с его <|>.

Идея

В конце прошлого урока ты упёрся в стену. Возьми двухаргументную функцию и подсунь её функтору:

ghci> fmap (+) (Just 3)
-- :: Maybe (Int -> Int)

Получился Just (3 +), функция, застрявшая внутри Maybe. У тебя есть Just (3 +) и есть Just 5. Применить одно к другому fmap не умеет: он берёт чистую функцию слева, а тут функция сама обёрнута. Нужен оператор, который применяет обёрнутую функцию к обёрнутому значению. Это <*>, а класс называется Applicative:

class Functor f => Applicative f where
  pure  :: a -> f a
  (<*>) :: f (a -> b) -> f a -> f b

Functor f => в заголовке снова держит иерархию: аппликатив это функтор, умеющий больше. Два метода:

  • pure кладёт обычное значение в структуру самым простым способом: pure 5 :: Maybe Int это Just 5.
  • <*> (читается ap или apply) применяет обёрнутую функцию к обёрнутому аргументу.
ghci> Just (+3) <*> Just 5
Just 8
ghci> Just (+) <*> Just 3 <*> Just 5
Just 8
ghci> Nothing <*> Just 5
Nothing
ghci> Just (+3) <*> Nothing
Nothing

Третья строка это разгадка. Цепочка читается слева направо: (Just (+) <*> Just 3) даёт Just (3 +), затем <*> Just 5 доводит до Just 8. А привычная связка это fmap плюс <*>:

ghci> (+) <$> Just 3 <*> Just 5
Just 8

Запомни этот шаблон как идиому: f <$> x <*> y <*> z применяет чистую функцию f к нескольким аргументам в структуре. <$> заводит цепочку, <*> продолжает. Если хоть один аргумент Nothing, весь результат Nothing: эффект структуры распространяется на результат.

liftA2

Связка f <$> x <*> y встречается так часто, что у неё есть имя, liftA2:

liftA2 :: Applicative f => (a -> b -> c) -> f a -> f b -> f c
liftA2 f x y = f <$> x <*> y
ghci> liftA2 (+) (Just 3) (Just 5)
Just 8
ghci> liftA2 (,) (Just 1) (Just 'a')
Just (1,'a')

liftA2 это fmap для функций двух аргументов: подними обычную двухместную функцию до работы внутри структуры.

Законы

Их четыре, и формально они громоздкие, но смысл один: pure и <*> обязаны вести себя предсказуемо, ничего не теряя и не добавляя. Самый наглядный закон тождества:

-- pure id <*> v  ==  v

pure id это обёрнутая функция-тождество, применённая к v, обязана вернуть v. Остальные три (гомоморфизм, обмен, композиция) гарантируют, что порядок упаковки и применения не влияет на результат. На практике их соблюдает любой честный инстанс, а заучивать формулы наизусть не нужно: важно понимать, что они есть и зачем.

премиум-урок

Продолжение в премиуме: 1 200 ₽ в месяц или 10 000 ₽ в год. Вместе с ним серверные проверки задач и школьные токены AI-коуча.

Оплата пока не подключена: премиум и курсы открываются промокодом.