Почему <$> и <*> вводят ввод в порядке, противоположном >> =?
Я понимаю аргументацию подписи типа <$>
, так как это просто инфиксная версия fmap
, но, сравнивая ее с тиковой подписью >>=
, это делает меня намного менее понятным.
Сначала установите, что я имею в виду.
(>>=) :: Monad m => m a -> (a -> m b) -> m b
(<*>) :: Applicative f => f (a -> b) -> f a -> f b
(<$>) :: Functor f => (a -> b) -> f a -> f b
Посмотрев на типы сигнатур, мы увидим, что >>=
берет значение слева, а функцию справа, что имеет большой смысл, если вы считаете его свойство цепочки: foo >>= bar >>= baz
Что заставляет меня задаваться вопросом, почему бы не <*>
и <$>
сделать это тоже? вы не можете написать foo <*> bar <*> baz
, потому что для вывода foo <*> bar
для вывода будет функция, а не значение.
Я знаю, что существуют <**>
и =<<
, которые оба переворачивают порядок параметров, позволяя мне делать что-то вроде:
Just 4 <**> pure (+3) <**> pure (*2) >>= (\x -> Just (x-3))
Что могло бы быть красиво сведено к:
Just 4 <$$> (+3) <$$> (*2) >>= (\x -> Just (x-3))
Если <$$>
существовало, или если порядок параметров <$>
и <*>
был отменен.
Еще одна вещь, которая заставляет меня задаться вопросом, почему существует разница, заключается в том, что для новичков становится труднее привыкнуть и/или помнить, является ли это функцией или значением, которое на первом месте, без необходимости искать его.
Итак, почему это в случаях <*>
и <$>
it fn op val
, но с >>=
it val op fn
?
Ответы
Ответ 1
Ответ на "почему он принимает параметры в этом порядке" в основном "потому что". Тот, кто определил эти функции, считал, что это лучший способ. (И это, вероятно, не было одним и тем же человеком в каждом случае.) Я, однако, приведу несколько примеров:
Предположим, что у нас есть синтаксическая монада. Предположим также, что мы определили
data Foobar = Foobar X Y Z
parseFoo :: Parser X
parseBar :: Parser Y
parseBaz :: Parser Z
Тогда мы можем написать
parseFoobar :: Parser Foobar
parseFoobar = do
foo <- parseFoo
bar <- parseBar
baz <- parseBaz
return (Foobar foo bar baz)
Или, явно,
parseFoobar =
parseFoo >>= \ foo ->
parseBar >>= \ bar ->
parseBaz >>= \ baz ->
return (Foobar foo bar baz)
Теперь напишите, что аппликативный стиль:
parseFoobar = return Foobar <*> parseFoo <*> parseBar <*> parseBaz
или, альтернативно,
parseFoobar = Foobar <$> parseFoo <*> parseBar <*> parseBaz
Если мы предположим, что <**> = flip <*>
существует (и имеет правильную ассоциативность), то мы имеем
parseFoobar = parseBaz <**> parseBar <**> parseFoo <**> return Foobar
Это выглядит странно. С функцией в конце и аргументами в обратном порядке? Зачем вам писать так? (Обратите внимание, что любые эффекты также находятся в обратном порядке.)
В монадической версии эффекты происходят сверху вниз. В аппликативной версии эффекты происходят слева направо. Это кажется "естественным".
Ответ 2
Не позволяйте монадам мешать здесь. Подумайте о приложении.
Для сравнения:
(<$>) :: Functor f => (a -> b) -> f a -> f b
и
($) :: (a -> b) -> a -> b
Вы можете видеть, что между обычным приложением и приложением под функтором существует соединение.
Общая информация: были предложения, чтобы использовать скобки для перегрузки пробелов (приложений), чтобы мы могли написать:
(| f a1 .. an |)
вместо
pure f <*> a1 <*> .. <*> an