Почему композиция Клейсли ожидает чистую ценность? - PullRequest
3 голосов
/ 24 апреля 2020

Это обычная реализация для композиции kleisli:

kleisli :: Monad m => (a -> m b) -> (b -> m c) -> a -> m c
kleisli = \f g x -> f x >>= g

Почему она не ожидает значения в контексте monadi c? Я уверен, что есть веская причина. Мне просто не удалось его увидеть.

kleisli' :: Monad m => (a -> m b) -> (b -> m c) -> m a -> m c
kleisli' = \f g x -> x >>= f >>= g

Тип кажется лучше сочетаемым, и return можно использовать, если у нас на сайте вызова есть только чистое значение.

Ответы [ 2 ]

4 голосов
/ 24 апреля 2020

Композиция Клейсли на самом деле является одним из самых простых способов ответить на часто задаваемый вопрос: для чего нужны монады?

Одна из самых полезных вещей, которые мы можем сделать с помощью обычных функций, - это их составление. Учитывая f :: a -> b и g :: b -> c, мы можем сначала выполнить f, а затем g для результата, что даст нам g . f :: a -> c.

Это фантастика c, пока нам остается работать с "обычными" функциями. Но как только мы начнем программировать в «реальном мире», мы, скорее всего, столкнемся с ситуациями, когда мы не сможем продолжать использовать такие функции, если наш язык останется чистым и ссылочно-прозрачным. Действительно, в таких ситуациях другие языки, которые менее принципиальны, чем Haskell, отказываются от любых притязаний быть чистыми. Рассмотрим следующие повседневные ситуации:

  • наша функция f может иногда не возвращать значение. Во многих других языках это будет обозначаться возвращением null, но вы не можете затем вставить его в g. (Конечно, вы можете адаптировать g, чтобы справиться с null входами, но это быстро станет повторным.)

В Haskell у нас нет null с, у нас есть конструктор типа Maybe, чтобы явно указать, что может не быть значения. Это означает, что f должен иметь тип a -> Maybe b. g будет иметь тип b -> Maybe c по той же причине. Но при этом мы утратили способность объединять две функции, поскольку мы не можем напрямую передать значение типа Maybe b тому, которое ожидает ввода типа b.

  • результат f может зависеть от некоторых побочных эффектов (например, ввода от пользователя или результата запроса к базе данных). В нечистых языках это не проблема, но в Haskell, чтобы сохранить чистоту, мы должны реализовать это в виде функции типа a -> IO b. Еще раз, g получит ту же форму, b -> IO c, и мы утратили способность наивно составлять две функции.

Я уверен, что вы видите, куда это идет. В обоих случаях (и более легко можно было бы предоставить по одному для каждой монады) нам пришлось заменить простую функцию типа a -> b на функцию типа a -> m b, чтобы учесть конкретный тип «побочного эффекта» - или, если хотите, какой-то особый вид «контекста», который применяется к результату функции. И при этом мы теряем способность составлять две функции, которые у нас были в мире «без побочных эффектов».

Для чего монады действительно нужны, так это для преодоления этого, и давайте восстановим форму композиции для такие "нечистые функции". Это, конечно, именно то, что дает нам композиция Клейсли, композиция функций вида a -> m b, которая удовлетворяет в точности тем свойствам, которые мы ожидаем от композиции функций (а именно, ассоциативности, и «функции идентичности» для каждого типа, которая здесь равна * 1044). *).

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

4 голосов
/ 24 апреля 2020

Стрелка Клейсли от a до b определяется как функция a -> m b. Давайте отметим это a ~> b (оставляя m предполагаемым). Что значит составить две из этих стрел? Он должен иметь такой тип:

(<=<) :: (b ~> c) -> (a ~> b) -> (a ~> c)

Теперь, если мы расширим это:

(<=<) :: (b -> m c) -> (a -> m b) -> (a -> m c)

И вот оно у вас. Похоже, вы смотрите на перевернутую версию (>=>), но это та же идея.

Эти операторы определены в Control.Monad.

Существует также более формальное определение стрелок Клейсли в стандартной библиотеке.

newtype Kleisli m a b = Kleisli { runKleisli :: a -> m b }

Он поставляется с экземпляром Category, который реализует эту композицию как оператор (.) (но вы должны использовать futz вокруг с оберткой нового типа).

...