(Recall that monads are not a good topic for your first blog post.)
I intend to bootstrap a blog by writing about 2 of my old papers, Monad Transformers for Backtracking Search and The Selection Monad as a CPS Transformation. (Wall Street will be spared for the time being.)
I’m going to write about this little bit of Haskell code:
import Control.Monad.Cont sat n = runCont $ sequence $ replicate n $ cont $ \k -> k $ k True
It’s a SAT solver: you give it a boolean function, and the number of variables to search, and it decides whether that function will ever return true for any values of those variables.
How does this work? Haven’t the foggiest. If anyone can explain what it does at runtime, there’s probably a research paper in it for you. If you can also predict how long it takes, that’s a big deal.
So where does this come from? It’s a demonstration I wrote of the work of Martin Escardó and Paulo Oliva on selection functions. For Haskell programmers, good places to start are Seemingly Impossible Functional Programs and What Sequential Games, the Tychonoff Theorem and the Double-Negation Shift Have in Common. I removed all traces of selection functions, using only the continuation monad that we all know and love, to make it as short and magical-looking as I could.
The first bit of magic is the function
\k -> k $ k True. Let’s call it
e, as in
e k = k $ k True
e is an existential quantifier. That is to say it takes a predicate (a function
k into booleans), and decides whether there exists an input making the predicate true. For example, on natural numbers, applying an existential quantifier to the predicate
\n -> n^2 == 4 should return
True, but applying it to
\n -> n^2 == 2 should return
e can’t exactly do that. In fact, the existential quantifier for natural numbers isn’t computable. What it does is to compute the existential quantifier for booleans.
I don’t have a slick way to demonstrate this counter-intuitive fact. There’s an awkward case-based argument, starting from the fact that
k True is either true, or it’s false. But this is only ε more enlightening than just brute-forcing it: there are precisely 4 (total) functions
Bool -> Bool, so it’s easy to just evaluate
e on each of them and check that it does the right thing.
So, we have the function
\k -> k $ k True :: (Bool -> Bool) -> Bool. We hit that with
cont which, despite not beginning with a capital letter, is the constructor of the
Cont monad. So it doesn’t actually do anything, except changing the type to
Cont Bool Bool. (The
runCont on the outside is the corresponding deconstructor, and also does nothing except change the type.)
Next we use
replicate n, which creates a list of
n identical copies. So at this point, we have a list containing
n instances of the existential quantifier, each one wrapped inside
Cont, the whole thing having type
[Cont Bool Bool].
The actual deep magic in the code snippet is the use of
sequence, possibly the most magical function in Prelude. What
sequence does, in general, is fold the monoidal product of a monoidal monad. For example, a useful little idiom in functional programming is to use
sequence to find the cartesian product of lists (because cartesian product is the monoidal product of the list monad).
It turns out that if you take the monoidal product of existential quantifiers in the
Cont monad, you get one big existential quantifier for the product type. Which means, we get an existential quantifier for boolean lists of length
n. Which is to say, if we give it a predicate
k :: [Bool] -> Bool, it will decide if there is an input of length
n making the predicate true. Which is another way of saying, it is a SAT solver for boolean functions with
No, I’m not going to prove that here.
There are some pretty big open questions about this code I alluded to earlier, of which the biggest is that there doesn’t seem to be any sort of complexity theory even capable of asking the question of what its runtime is. (The real difficulty is that
sequence used this way is a third-order function, that is, its input is a function which itself takes a function as input.)
Instead, I’m going to ask a more manageable and concrete question: Can this code be written in continuation-passing style? By that I mean, can it be written in a way that doesn’t use
cont, but instead uses
call/cc? Equivalently, can this code be translated to Scheme?