开发者

Iterated substitution in Haskell via the Identity Monad

As far as I can tell, one of the big uses of the monadic bind operation is to do variable substitution such as

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

This is all well and good if maybeComputeSomething, maybeComputeSomethingElse, and maybeDoStuff all return Maybe values, but I feel like this form of iterated substitution would be useful in computations even if there wasn't the possibility of a returned Nothing. Since the identity is a monad, I would in some sense expect to be able to do something such as

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

which would expand to something looking like

computeSomething >>= (\x -> 
    computeSomethingElse >>= (\y ->
        doStuf开发者_JS百科f x y))

and since bind in the identity monad is simply x >>= f = f x, this would act as

doStuff computeSomething computeSomethingElse

if >>= were treated as the identity monad's bind, but this (unsurprisingly) fails, because after all I never explicitly stated a monad (unlike the Maybe example when the types are clearly of the form Maybe a) and if Haskell assumed the identity monad everywhere the majority of cases would get cluttered up quite fast by the required disambiguations.

So this leads me to my question. I want some code that feels like

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

and that does the same thing as

doStuff computeSomething computeSomthingElse

I understand that I could do this by explicitly defining an Id monad which looks like Maybe except without the Nothing possibility, but this takes types a -> Id a, and so doesn't really define the identity monad. Furthermore, my functions now have to have types a -> Id b, whereas I would really like them to still be of the form a -> b. Is there a way to create the repeated substitution feel without introducing complexity in the types involved?


Sadly the identity monad needs at least a newtype wrapper to dispatch upon.

The reason makes sense when you consider overlapping instances. A monad instance that was somehow constructed for

type Id a = a 

would always apply, subsuming any of the more interesting monads.

Therefore you have to implement it as something like

newtype Id a = Id a

Then

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

This gives a sufficient hint to the type system to know when you really want to work with this monad. Note: This is an artifact of typeclass dispatch. In languages that offer ML-like modules or Scala, where you don't have typeclass inference to help (and conversely to worry about) you can define a true identity monad, but then you are forced to route all calls to that particular >>= by yourself, so it turn to be a wash or a net loss anyways.

Now you can

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

but the types are

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

and if you made Id into an instance of Applicative the syntax could reduce to

doStuff <$> computeSoemthing <*> computeSomethingElse

so each of those methods would have to explicitly apply the Id newtype wrapper or wrap their result in return.

In practice it is better to use let bindings in this context.

let
   x = computeSomething
   y = computeSomethingElse
in doStuff x y

or more idiomatically, a where clause

doStuff x y where
  x = computeSomething
  y = computeSomethingElse

or even in this case, since those are so small, and to come full circle to the Applicative example above:

doStuff computeSomething computeSomethingElse


Note this is really a comment, but I've made it an answer to allow formatting...

It looks like you just want a function similar to liftM2, except the post-combine operation is monadic rather than pure - e.g.

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

Compare with liftM2 where the post-combiner (the first argument) is pure:

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

The monady2 combinator doesn't exist in the standard libraries, but you are free to define it.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜