I thought the right type for ContT should be
newtype ContT m a = ContT {runContT :: forall r. (a -> m r) -> m r}
and other control operators
shift :: Monad m => (forall r. (a -> ContT m r) -> ContT m r) -> ContT m a
reset :: Monad m => ContT m a -> ContT m a
callCC :: ((a -> (forall r. ContT m r)) -> ContT m a) -> ContT m a
Unfortunately, I can not make callCC type check, and don’t know how to do it.
I managed to make shift and reset type check
reset :: Monad m => ContT m a -> ContT m a
reset e = ContT $ \ k -> runContT e return >>= k
shift :: Monad m => (forall r. (a -> ContT m r) -> ContT m r) -> ContT m a
shift e = ContT $ \ (k :: a -> m r) ->
runContT ((e $ \ v -> ContT $ \c -> k v >>= c) :: ContT m r) return
but still, I can’t use shift and reset in recursive jumpings like this?
newtype H r m = H (H r m -> ContT m r)
unH (H x) = x
test = flip runContT return $ reset $ do
jump <- shift (\f -> f (H f))
lift . print $ "hello"
unH jump jump
Have anyone tried this before?
Would you like to play a game?
Today, you get to be
callCC.Everything to the left of that function arrow are the moves your opponent has made. To the right of the arrow is the end of the game. To win, you must construct something matching the right side using only the pieces your opponent has provided.
Fortunately, you still have some say in matters. See this arrow here?
When you receive something that itself contains an arrow, everything to the left of that represents moves you get to make, and the part to the right the end of that game branch, giving you another piece you can use as part of your (hopefully) winning strategy.
Before we go further, let’s simplify a few things: The monad transformer aspect is merely a distraction, so discard that; and add explicit quantifiers for every type variable.
Now, think about what a type like
forall a. ...amounts to. If you produce something with a type like that, you’re saying you can provide a value for any typeawhatsoever. If you receive something with a type like that, you can pick a specific type to use. Compare that to a type likeA -> ...for a monomorphic function; producing such a function says that you know how to provide a result for any value of typeA, while receiving such a function lets you pick a specific valueAto use. This seems to be the same situation as withforall, and in fact the parallel holds. So, we can treatforallas indicating a move where you or your opponent gets to play a type, rather than a term. To reflect this, I’ll abuse notation and writeforall a. ...asa =>; we can then treat it just like(->)except that it must appear to the left of any uses of the type variable being bound.We can also note that the only thing that can be done directly with a value of type
Cont ais applyingrunContto it. So we’ll do that in advance, and embed all the relevant quantifiers directly into the type forcallCC.Because we’re able to treat
foralljust like other function arrows, we can reorder things and remove superfluous parentheses to tidy things up a bit. In particular, note thatcallCCisn’t actually the end of the game, as it turns out; we have to provide a function, which amounts to providing another game to play in which we again take the role of the rightmost arrow. So we can save a step by merging those. I’ll also float type arguments to the left to get them all in one place.So… our move.
We need something of type
r3. Our opponent has made four moves, which we’ve received as arguments. One move is to chooser3, so we’re at a disadvantage already. Another move isa -> r3, meaning that if we can play ana, our opponent will cough up anr3and we can coast to victory. Unfortunately, our opponent has also playeda, so we’re back where we started. We’ll either need something of typea, or some other way to get something of typer3.The final move our opponent made is more complicated, so we’ll examine it alone:
Remember, this is the move they made. So the rightmost arrow here represents our opponent, and everything to the left represents the type of moves we can make. The result of this is something of type
r2, wherer2is something we get to play. So clearly we’ll need to play eitherr3orato make any progress.Playing
a: If we playaasr2, then we can playidasa -> r2. The other move is more complicated, so we’ll jump inside that.Back to the rightmost arrow representing us. This time we need to produce something of type
r1, wherer1is a move the opponent made. They also played a functionb -> r1, where the typebwas also a move they made. So we need something of either typeborr1from them. Unfortunately, all they’ve given us is something of typea, leaving us in an unwinnable position. Guess playingaearlier was a bad choice. Let’s try again…Playing
r3: If we playr3asr2, we also need to play a functiona -> r3; fortunately, the opponent already played such a function, so we can simply use that. Once again we jump inside the other move:…only to find that it’s exactly the same impossible situation as before. Being at the mercy of the opponent’s choice of
r1with no requirement that they provide a way to construct one leaves us trapped.Perhaps a bit of trickery will help?
Bending the rules — playing
r1:We know that in regular Haskell we can rely on laziness to twist things around and let computations swallow their own tail. Without worrying too much about how, let’s imagine that we can do the same here–taking the
r1that our opponent plays in the inner game, and pulling it out and back around to play it asr2.Once again, here’s the opponent’s move:
After our knot-tying shenanigans, it ends up equivalent to this:
The type arguments have disappeared thanks to our trickery, but
r1is still chosen by the opponent. So all we’ve accomplished here is shuffling things around; there’s clearly no way we can even hope to get anaorr3out of this, so we’ve hit another dead end.So we make one final, desperate attempt:
Bending the rules — playing
b:This time we take the
bplayed by the opponent in the innermost game and loop that around to play asr2. Now the opponent’s move looks like this:And then back in the inner game:
Continuing the trickery we can twist the
bresult above around as well, to give to the functionb -> r1, receiving ther1we need. Success!Stepping back out, we still have one problem left. We have to play something of type
a -> b. There’s no obvious way to find one, but we already have ablying around, so we can just useconston that to discard theaand––but wait. Where’s that value of type
bcoming from in the first place? Putting ourselves briefly in our opponent’s shoes, the only places they can get one are from the results of the moves we make. If the onlybwe have is the one they give us, we’ll end up going around in circles; the game never ends.So, in the game of
callCC, the only strategies we have lead to either a loss or a permanent stalemate.Alas, it seems that the only winning move is not to play.