Повторное замещение в Haskell через монаду идентификации - PullRequest
1 голос
/ 25 марта 2011

Насколько я могу судить, одним из основных применений операции монадического связывания является подстановка переменных, например

do x <- maybeComputeSomething
   y <- maybeComputeSomethingElse
   maybeDoStuff x y

Это все хорошо, если MaybeComputeSomething, MaybeComputeSomethingElse и MaybeDoStuff все возвращают значения Maybe, но я чувствую, что такая форма многократного замещения будет полезна в вычислениях, даже если не было возможности вернуть ничего. Поскольку личность является монадой, я бы в некотором смысле ожидал, что смогу что-то сделать, например

do x <- computeSomething
   y <- computeSomethingElse
   doStuff x y

, который расширился бы до чего-то похожего на

computeSomething >>= (\x -> 
    computeSomethingElse >>= (\y ->
        doStuff x y))

и поскольку связывание в монаде тождества просто x >> = f = f x, это будет действовать как

doStuff computeSomething computeSomethingElse

if >> = рассматривались как привязка идентификационной монады, но это (неудивительно) терпит неудачу, потому что в конце концов я никогда не указывал явно монаду (в отличие от примера Maybe, когда типы явно имеют форму Maybe a) и если Haskell Предполагалось, что монада идентичности везде, где большинство случаев достаточно быстро загромождается необходимыми неоднозначностями.

Так что это подводит меня к моему вопросу. Я хочу код, который выглядит как

do x <- computeSomething
   y <- computeSomethingElse
   doStuff x y

и это то же самое, что и

doStuff computeSomething computeSomthingElse

Я понимаю, что я мог бы сделать это, явно определив монаду Id, которая выглядит как «Может быть», но без возможности «Ничего», но это принимает типы a -> Id a, и, таким образом, на самом деле не определяет монаду идентификации. Кроме того, мои функции теперь должны иметь типы a -> Id b, тогда как мне бы очень хотелось, чтобы они по-прежнему имели форму a -> b. Есть ли способ создать ощущение повторной подстановки, не внося сложности в задействованные типы?

Ответы [ 2 ]

7 голосов
/ 25 марта 2011

К сожалению, монаде для идентификации требуется как минимум оболочка нового типа для отправки.

Причина имеет смысл, когда вы рассматриваете перекрывающиеся экземпляры.Экземпляр монады, который был каким-то образом сконструирован для

type Id a = a 

, всегда будет применяться, включая любую из более интересных монад.

Поэтому вы должны реализовать его как что-то вроде

newtype Id a = Id a

Тогда

instance Monad Id where
    return = Id
    Id a >>= f = f a

Это дает достаточную подсказку системе типов, чтобы знать, когда вы действительно хотите работать с этой монадой.Примечание. Это артефакт диспетчеризации типов.В языках, которые предлагают ML-подобные модули или Scala, где у вас нет вывода классов типов, чтобы помочь (и, наоборот, беспокоиться), вы можете определить истинную идентификационную монаду, но затем вы будете вынуждены перенаправлять все вызовы на этот конкретный >>= в одиночку, так что в любом случае это будет стирка или чистая потеря.

Теперь вы можете

do x <- computeSomething
   y <- computeSomethingElse
   doStuff x y

, но типы:

computeSomething :: Id a
computeSomethingElse :: Id b
doStuff :: a -> b -> Id c

, и есливы сделали Id экземпляром Applicative, синтаксис которого может быть уменьшен до

doStuff <$> computeSoemthing <*> computeSomethingElse

, поэтому каждому из этих методов придется явно применять оболочку нового типа Id или обернуть их результат в ответ.

Inна практике лучше использовать привязки let в этом контексте.

let
   x = computeSomething
   y = computeSomethingElse
in doStuff x y

или более идиоматически, выражение where

doStuff x y where
  x = computeSomething
  y = computeSomethingElse

или даже в этом случае, поскольку они настолько малы, ипройти полный круг к приведенному выше Приложительному примеру:

doStuff computeSomething computeSomethingElse
1 голос
/ 25 марта 2011

Обратите внимание, что это действительно комментарий, но я сделал ответ, чтобы разрешить форматирование ...

Похоже, вы просто хотите функцию, подобную liftM2 , за исключением того, что операция после объединения является монадической, а не чистой - например

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

Сравните с liftM2, где посткомбинатор (первый аргумент) чистый:

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

Комбинатор monady2 не существует в стандартных библиотеках, но вы можете его определить.

...