Понимание того, как применяются аппликативные функторы haskell
У меня просто небольшой вопрос о аппликативных функторах, чтобы помочь мне понять их. Это просто материал, который я применяю в GHCI.
[(+3),((-) 3),(*3)] <*> [4]
[7,-1,12]
Это имеет смысл для меня. Основное применение. Но при попытке:
[(Just (+3)),(Just ((-) 3)),(Just (*3))] <*> [Just 4]
Мне дают большое количество ошибок. Я немного понимаю почему; есть два конструктора данных ([]
а также Maybe
) и <*>
Функция только "отклеивает" один из них. Что я хотел бы помочь в моем понимании, так это то, что именно haskell пытается сделать шаг за шагом, пока он не потерпит неудачу, и как вы могли бы обойти это и успешно рассчитать на:
[(Just 7),(Just -1),(Just 12)]
1 ответ
У вас есть два разных Applicative
экземпляров. Правда, что
Just (* 3) <*> Just 4 == Just 12
но []
Экземпляр просто пытается применить каждую "функцию" в первом списке к каждому значению во втором, так что вы в конечном итоге пытаетесь применить
(Just (* 3)) (Just 4)
что является ошибкой
(Точнее, ваш список Just
значения просто имеют неправильный тип, чтобы действовать в качестве первого аргумента для <*>
.)
Вместо этого вам нужно на карту <*>
по первому списку.
> (<*>) <$> [(Just (+3)),(Just ((-) 3)),(Just (*3))] <*> [Just 4]
[Just 7,Just (-1),Just 12]
(Сопоставление функции высшего порядка со списком - это то, как вы обычно получаете список обернутых функций. Например,
> :t [(+3), ((-) 3), (* 3)]
[(+3), ((-) 3), (* 3)] :: Num a => [a -> a]
> :t Just <$> [(+3), ((-) 3), (* 3)]
Just <$> [(+3), ((-) 3), (* 3)] :: Num a => [Maybe (a -> a)]
)
Data.Functor.Compose
упоминается в комментариях еще один вариант.
> import Data.Functor.Compose
> :t Compose [(Just (+3)),(Just ((-) 3)),(Just (*3))]
Compose [(Just (+3)),(Just ((-) 3)),(Just (*3))]
:: Num a => Compose [] Maybe (a -> a)
> Compose [(Just (+3)),(Just ((-) 3)),(Just (*3))] <*> Compose [Just 4]
Compose [Just 12,Just (-1),Just 12]
> getCompose <$> Compose [(Just (+3)),(Just ((-) 3)),(Just (*3))] <*> Compose [Just 4]
[Just 12,Just (-1),Just 12]
Определение Compose
очень просто:
newtype Compose f g a = Compose { getCompose: f (g a) }
Магия в том, что пока f
а также g
оба (аппликативные) функторы, то Compose f g
также (аппликативный) функтор.
instance (Functor f, Functor g) => Functor (Compose f g) where
fmap f (Compose x) = Compose (fmap (fmap f) x)
instance (Applicative f, Applicative g) => Applicative (Compose f g) where
pure x = Compose (pure (pure x))
Compose f <*> Compose x = Compose ((<*>) <$> f <*> x)
в Applicative
Например, вы можете увидеть такое же использование (<*>) <$> ...
что я использовал выше.