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-коуча.
Оплата пока не подключена: премиум и курсы открываются промокодом.