Подтвердить что ты не робот

Почему <$> и <*> вводят ввод в порядке, противоположном >> =?

Я понимаю аргументацию подписи типа <$>, так как это просто инфиксная версия 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?

4b9b3361

Ответ 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