Является ли оператор monad bind (>> =) ближе к функциональному составу (цепочке) или приложению функции?

Во многих статьях я прочитал, что оператор monad >>= - это способ представления композиции функции. Но для меня это ближе к некоторому расширенному функциональному приложению

($)   :: (a -> b) -> a -> b
(>>=) :: Monad m => m a -> (a -> m b) -> m b

Для композиции мы имеем

(.)   :: (b -> c) -> (a -> b) -> a -> c
(>=>) :: Monad m => (a -> m b) -> (b -> m c) -> a -> m c

Просьба пояснить.

Ответ 1

Ясно, что >>= не является способом представления композиции функции. Состав функции просто выполняется с помощью .. Тем не менее, я не думаю, что любая из статей, которые вы прочитали, означала это.

То, что они имели в виду, это "модернизация" композиции функций для работы непосредственно с "монадическими функциями", то есть с функциями формы a -> m b. Техническим термином для таких функций являются стрелки Kleisli, и действительно они могут быть составлены с помощью <=< или >=>. (В качестве альтернативы вы можете использовать Category экземпляр, тогда вы также можете составить их с помощью . или >>>.)

Однако разговоры о стрелках/категориях, как правило, путают особенно для новичков, так же как точечные определения обычных функций часто сбивают с толку. К счастью, Haskell позволяет нам выражать функции также в более привычном стиле, который фокусируется на результатах функций, а не на самих функциях, как на абстрактных морфизмах & dagger;. Это делается с абстракцией лямбда: вместо

q = h . g . f

вы можете написать

q = (\x -> (\y -> (\z -> h z) (g y)) (f x))

... Конечно, предпочтительным стилем будет (это всего лишь синтаксический сахар для абстракции лямбда!) & ddagger;

q x = let y = f x
          z = g y
      in h z

Обратите внимание, что в выражении лямбда в основном композиция была заменена приложением:

q = \x -> (\y -> (\z -> h z) $ g y) $ f x

Адаптировано к стрелкам Kleisli, это означает вместо

q = h <=< g <=< f

вы пишете

q = \x -> (\y -> (\z -> h z) =<< g y) =<< f x

который снова выглядит, конечно, намного приятнее с переворачиваемыми операторами или синтаксическим сахаром:

q x = do y <- f x
         z <- g y
         h z

Итак, =<< соответствует <=<, как $ соответствует .. Причина, по которой все еще имеет смысл называть его композиционным оператором, состоит в том, что помимо "применения к значениям" оператор >>= также выполняет нетривиальный бит о композиции стрелки Клейсли, для которой не нужна композиция функций: объединение монадических слоев.


& dagger; Причина этого в том, что Hask является декартовой замкнутой категорией, в особенно четко обозначенная категория. В такой категории стрелки могут, в широком смысле, быть определены совокупностью всех их результатов при применении к простым значениям аргументов.

& ddagger; @adamse отмечает, что let не является синтаксическим сахаром для абстракции лямбда. Это особенно актуально в случае рекурсивных определений, которые вы не можете напрямую писать с помощью лямбда. Но в простых случаях, подобных этому, let ведет себя как синтаксический сахар для лямбда, так же, как do обозначение - синтаксический сахар для лямбда и >>=. (Кстати, есть расширение, которое допускает рекурсию даже в нотации do... оно обходит ограничение лямбда с помощью комбинаторов с фиксированной запятой.)

Ответ 2

В качестве иллюстрации рассмотрим следующее:

($)                ::   (a -> b) ->   a ->   b
let g=g in (g  $)  ::                 a ->   b
            g      ::   (a -> b)
                                     _____
Functor f =>                        /     \
(<$>)              ::   (a -> b) -> f a -> f b
let g=g in (g <$>) ::               f a -> f b
            g      ::   (a -> b) 
                       ___________________
Applicative f =>      /             /     \
(<*>)              :: f (a -> b) -> f a -> f b
let h=h in (h <*>) ::               f a -> f b
            h      :: f (a -> b)
                             _____________
Monad m =>                  /.------.     \
(=<<)              :: (a -> m b) -> m a -> m b
let k=k in (k =<<) ::               m a -> m b
            k      :: (a -> m b)

Итак, да, каждый из них (g <$>), (h <*>) или (k =<<) - это какое-то приложение-функция, которое продвигается либо в Functor, Applicative Functor, либо в контекст Monad. И (g $) - это просто регулярный вид регулярного вида функции.

С функторами функции не влияют на компонент f общей вещи. Они работают строго внутри и не могут влиять на "обертывание".

С помощью Applicatives функции завершаются в f, которые обертываются вместе с аргументом (как частью приложения) для создания обертки результата.

С помощью Monads, сами функции теперь производят завернутые результаты, как-то вытягивая свои аргументы из завернутого аргумента (как часть приложения).

Мы можем видеть три оператора как некоторую маркировку для функции, например, математики, как писать f' или f^ или f* (и в оригинальной работе Эугенио Могги (1 )f* - это именно то, что было использовано, обозначая продвинутую функцию (f =<<)).

И, конечно же, с продвинутыми функциями :: f a -> f b мы получаем цепочку, потому что теперь типы выстраиваются в линию. Продвижение - это то, что позволяет композицию.


(1) "Понятия о вычислении и монадах", Евгенио Могги, июль 1991 года.


Таким образом, функтор "магически работает внутри" "трубы"; аппликативным является "предварительно изготовленные трубы, изготовленные из компонентов заранее"; и монады "строят трубопроводные сети, когда мы идем". Иллюстрация:

введите описание изображения здесь