How to make callCC more dynamic?

前端 未结 2 456
忘了有多久
忘了有多久 2020-12-29 05:24

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 op

2条回答
  •  被撕碎了的回忆
    2020-12-29 05:50

    Would you like to play a game?

    Today, you get to be callCC.

    callCC :: ((a -> (forall r. ContT m r)) -> ContT m a) -> ContT m a
    --                                       you are here ^^
    

    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?

    callCC :: ((a -> (forall r. ContT m r)) -> ContT m a) -> ContT m a
    --                this is your opponent ^^
    

    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.

    callCC :: forall a. ((a -> (forall b. Cont b)) -> Cont a) -> Cont a
    

    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 type a whatsoever. If you receive something with a type like that, you can pick a specific type to use. Compare that to a type like A -> ... for a monomorphic function; producing such a function says that you know how to provide a result for any value of type A, while receiving such a function lets you pick a specific value A to use. This seems to be the same situation as with forall, and in fact the parallel holds. So, we can treat forall as 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 write forall a. ... as a =>; 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 a is applying runCont to it. So we'll do that in advance, and embed all the relevant quantifiers directly into the type for callCC.

    callCC :: a => ( (a -> (b => (r1 => (b -> r1) -> r1))) 
                   -> (r2 => (a -> r2) -> r2)
                   ) -> (r3 => (a -> r3) -> r3)
    

    Because we're able to treat forall just like other function arrows, we can reorder things and remove superfluous parentheses to tidy things up a bit. In particular, note that callCC isn'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.

    callCC :: a => r3 => (a -> r3) 
           -> (r2 => (b => r1 => a -> (b -> r1) -> r1) -> (a -> r2) -> r2) 
           -> r3
    

    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 choose r3, so we're at a disadvantage already. Another move is a -> r3, meaning that if we can play an a, our opponent will cough up an r3 and we can coast to victory. Unfortunately, our opponent has also played a, so we're back where we started. We'll either need something of type a, or some other way to get something of type r3.

    The final move our opponent made is more complicated, so we'll examine it alone:

    r2 => (b => r1 => a -> (b -> r1) -> r1) -> (a -> r2) -> r2
    

    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, where r2 is something we get to play. So clearly we'll need to play either r3 or a to make any progress.

    Playing a: If we play a as r2, then we can play id as a -> r2. The other move is more complicated, so we'll jump inside that.

    b => r1 => a -> (b -> r1) -> r1
    

    Back to the rightmost arrow representing us. This time we need to produce something of type r1, where r1 is a move the opponent made. They also played a function b -> r1, where the type b was also a move they made. So we need something of either type b or r1 from them. Unfortunately, all they've given us is something of type a, leaving us in an unwinnable position. Guess playing a earlier was a bad choice. Let's try again...

    Playing r3: If we play r3 as r2, we also need to play a function a -> r3; fortunately, the opponent already played such a function, so we can simply use that. Once again we jump inside the other move:

    b => r1 => a -> (b -> r1) -> r1
    

    ...only to find that it's exactly the same impossible situation as before. Being at the mercy of the opponent's choice of r1 with 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 r1 that our opponent plays in the inner game, and pulling it out and back around to play it as r2.

    Once again, here's the opponent's move:

    r2 => (b => r1 => a -> (b -> r1) -> r1) -> (a -> r2) -> r2
    

    After our knot-tying shenanigans, it ends up equivalent to this:

    (b => a -> (b -> r1) -> r1) -> (a -> r1) -> r1
    

    The type arguments have disappeared thanks to our trickery, but r1 is 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 an a or r3 out 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 b played by the opponent in the innermost game and loop that around to play as r2. Now the opponent's move looks like this:

    (r1 => a -> (b -> r1) -> r1) -> (a -> b) -> b
    

    And then back in the inner game:

    r1 => a -> (b -> r1) -> r1
    

    Continuing the trickery we can twist the b result above around as well, to give to the function b -> r1, receiving the r1 we 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 a b lying around, so we can just use const on that to discard the a and--

    --but wait. Where's that value of type b coming 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 only b we 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.

    callCC :: forall a. ((a -> (forall b . Cont b)) -> Cont a) -> Cont a
    callCC = error "A strange game..."
    

    Alas, it seems that the only winning move is not to play.

提交回复
热议问题