Есть такой деруцкий оператор, для апликативных функторов <*>
У него есть такой вот тип:
type Applicative :: (* -> *) -> Constraint
class Functor f => Applicative f where
...
(<*>) :: f (a -> b) -> f a -> f b
...
Что он делает? Он имеет два аргумента.
Первый — контейнер с функцией (функциями) Второй — контейнер с данными к которым функция будет применена
На выходе мы получим контейнер с результирующими данными.
Например:
>>> Just (+1) <*> Just 2
Just 3
>>> [(+1)] <*> [1,2,3]
[2,3,4]
>>> [(+1), (*0)] <*> [1,2,3]
[2,3,4,0,0,0]
>>> [show] <*> [1,2,3]
["1","2","3"]
Но что будет если написать?
>>> (zip <*> scanl1 max) [2,7,1,5,4,1]
[(2,2),(7,7),(1,7),(5,7),(4,7),(1,7)]
Это немного сильно вводит в заблуждение. Почему оно отработало?
По результату, то что мы получили эквивалентно
>>> zip [2,7,1,5,4,1] (scanl1 max [2,7,1,5,4,1])
[(2,2),(7,7),(1,7),(5,7),(4,7),(1,7)]
Но почему? И как <*>
смог прочитать функцию zip как контенер?
Давайте посмотрим что такое zip
>>> zip [1,2,3] [5,6,7]
[(1,5),(2,6),(3,7)]
>>> :t zip
zip :: [a] -> [b] -> [(a, b)]
То есть zip это функция, которая принимает два списка и сшивает их элементы между собой
Но что забавно, у zip оказывается есть тип. У каждой функции есть свой тип, это тип "функция", то есть (->)
Надо посмотреть что это такое
:info (->)
type (->) :: * -> * -> *
data (->) a b
-- Defined in ‘ghc-prim-0.6.1:GHC.Prim’
infixr -1 ->
instance Applicative ((->) r) -- Defined in ‘GHC.Base’
instance Functor ((->) r) -- Defined in ‘GHC.Base’
instance Monad ((->) r) -- Defined in ‘GHC.Base’
instance Monoid b => Monoid (a -> b) -- Defined in ‘GHC.Base’
instance Semigroup b => Semigroup (a -> b) -- Defined in ‘GHC.Base’
Как видите, на самом деле это даже не то чтобы тип, а конструктор типов (это легко сделать в хаскеле)
То есть, мы можем прочитать zip :: [a] -> [b] -> [(a, b)]
как
zip это тип (->)
который первым элементом принимает список [a]
, а вторым тип2, который первым элементом принимает тип [b]
, а вторым тип [(a,b)]
И у тип (->)
реализовывает интерфей Applicative
Давайте посмотрим как:
-- | @since 2.01
instance Applicative ((->) r) where
pure = const
(<*>) f g x = f x (g x)
liftA2 q f g x = q (f x) (g x)
То есть мы видим что (<*>)
принимает на вход f g x
и потом применяет g
к x
а к результату применяет функцию f
с первым аргументом x
то есть рельутатом (zip <*> scanl1 max) [2,7,1,5,4,1]
как раз будет zip [2,7,1,5,4,1] (scanl1 max [2,7,1,5,4,1])
который мы и определили в самом начале.
И вообще, я всегда думал что доклады на ютубе это какая-то фигня. Но вот из доклада который я посмотрел вчера ночью я получил интерес к тому как такая конструкция (zip <*> scanl1 max)
может работать и в итоге понял что функции тоже могут реализовывать интерфейсы и быть апликативными функторами или даже монадами. Может это уж и не такая и фигня.