Reputation: 10426
Obviously MonadCont
s is more restricted and gives more power than plain Monad
s, thanks to its callCC
. This means less instances of it, and more you can do with it.
When look at defined instances of MonadCont
, it looks like everything listed there require either Cont
or ContT
or an already existing MonadCont
instance. That means we have to start with some Cont
or ContT
and in particular cannot turn IO
into a MonadCont
.
However, I believe it makes sense to use callCC
in a IO
context, so we can simplify the following (adjusted from the official Hackage page callCC
example):
whatsYourName :: IO ()
whatsYourName = do
name <- getLine
let response = flip runCont id $ do
callCC $ \exit -> do
when (null name) (exit "You forgot to tell me your name!")
return $ "Welcome, " ++ name ++ "!"
print response
into
whatsYourName' :: IO ()
whatsYourName' = do
name <- getLine
response <- callCC $ \exit -> do
when (null name) (exit "You forgot to tell me your name!")
return $ "Welcome, " ++ name ++ "!"
print response
to use callCC
in a do block in a cleaner way.
Of cause, to make IO
an instance of MonadCont
we must have some magic, since callCC
for IO
means "call the given function with the future computation specifies what to happen next in the real world", so only the interpreter or the compiler can actually know what this mean. On the other hand, I didn't see any theoretical reason that this is importable, since Scheme already have it for a long time, and making such an instance requires no language change at all.
Possible issue
One factor I can think of is that the semantic of callCC
is conflict with proper cleanup guarantee. A lot of languages provides "try...finally" control for proper cleanup, and C++'s destructor is also garantee that. I am not sure what is it in Haskell, but if callCC
is available for IO
one can then use it to escape from any IO
involved context that requires cleanup, so providing sush a garantee will become impossible, as you can see what happens in Ruby.
Discussion of opinions
The answer from @jozefg is very good. I just want to write down my opinions here.
It is true that MonadCont
come from mtl. But that does not means GHC or other compiler cannot define a unsafeCallCC
and define the instance if MonadCont
with the correct definition is in scope of the compiling module and -XIOMonadCont
being set.
I already talked about exception safety and it looks hard to be sure about that. However, Haskell already have unsafePerformIO
, which basically even more unsafe than unsafeCallCC
, in my opinion.
Of cause callCC
is, in most case, too powerful and should be avoid when possible. However, in my opinion, continuation passing style can be used to make lazy evaluation explicit, which can help better understand the program and thus easier to find possible optimizations. Of cause CPS is not MonadCont
, but it is a natural step to use it and convert the deep nested inner functions into do notations.
Upvotes: 3
Views: 373
Reputation: 709
The answer is NO because call/cc is a bad feature for any language. Although call/cc has been in Scheme for a very long time, it does not mean that call/cc is a good language feature. The experience with call/cc in Scheme showed that it is a bad language feature: using call/cc often leaks memory, call/cc without dynamic-wind is unsuitable for any serious program or any library; on the other hand, with dynamic-wind the standard call/cc idioms become slow. All these drawbacks are enumerated, with supporting code and other evidence, in http://okmij.org/ftp/continuations/against-callcc.html
I agree that continuation-passing style has many benefits. In fact, when we write Haskell code in monadic style, we use exactly the CPS. Indeed, consider a simple nested function invocation putChar (getChar ())
. We write it in CPS as follows
getCharK :: () -> (Char -> w) -> w
putCharK :: Char -> (() -> w) -> w
gp :: (() -> w) -> w
gp = \k -> getCharK () (\c -> putCharK c k)
And here how we write the same nested invocation for monadic getM and putM (for some monad M):
getM :: () -> M Char
putM :: Char -> M ()
gpM :: M ()
gpM = getM () `bind` (\c -> putM c)
Now, if bind is defined as
bind m f = \k -> m (\x -> (f x) k)
then the monadic nested function application is identical to CPS version of the code.
The monadic example works in Haskell if you replace M with ContT w IO
and and newtype
wrappers ContT/runContT in the definition of bind (which becomes (>>=)
in the ContT w IO monad). So, Haskell already lets us write code in CPS; the do-nonation makes it quite convenient.
Upvotes: 1
Reputation: 53871
I'd say this is a bad idea. First of all, MonadCont
is in the MTL. GHC has no knowledge about it, this would mean making the compiler depend on a 3rd party library, ick.
Second, callCC
is unpopular even among some very high profile Schemers, mainly because it makes reasoning about the code a pain! In much the same way that goto
is hard to reason about. Particularly when in Haskell where'd we'd have to concerns
callCC
safe?Finally, we don't even need it. If you want to use continuations and IO, use ContT IO
, it's exactly as powerful. However, I almost guarantee that it can be replaced with something less powerful, like monad-prompt
. Continuations are a sledge-hammer, 9 out of 10 times, callCC
is too powerful and can be more pleasantly phrased using a combination of higher order functions and laziness.
As an example, one of the prototypical uses of callCC
is to implement something like exceptions, but in Haskell we can just use monads :) (which rely on higher order functions and laziness).
In essence what your proposing increases complexity, means merging the MTL into base, and a whole host of other unpleasantness to just avoid liftIO
.
MonadCont
of course :)This is a bit different, unsafePerformIO
is meant to be used so that the side effects aren't visible to anyone else since you have no guarantees how or when things are executed.
If this was the case with callCCIO
, then you could just use Cont
!
Continuation passing style is useful, and we have it, with ConT r IO
! This is the biggest nail in the coffin for me, I don't see any benefit for this idea over just using the existing library over a difficult and potentially unsafe compiler hack.
Upvotes: 9
Reputation: 1477
By using the ContT
monad for simple abort functionality you are effectively hitting a fly with a bazooka. :-) If your goal is just to abort returning an error message when something goes wrong, then you may want to consider using the ErrorT
monad (provided by transformers) instead. Alternatively, if you will not be using a separate type for the result when an error occurs, then you may want to consider using the AbortT
monad (provided by AbortT-transformers) instead.
Upvotes: 1
Reputation: 33637
The documentation says:
Many algorithms which require continuations in other languages do not require them in Haskell, due to Haskell's lazy semantics. Abuse of the Continuation monad can produce code that is impossible to understand and maintain
I guess this is probably the main reason.
Upvotes: 0