6

I just got over the hump of figuring out how to use the List monad to do nondeterministic computations. However I believe my algorithm would benefit from a breadth-first search instead of a depth-first one which you get from the List monad.

Here is an excerpt showing the interesting part of my algorithm. It is a solver for the logic puzzle Akari.

solve :: Game -> [Game]
solve game = do
        let ruleBasedSolverResult = applyRuleBasedSolversUntilSteady game
        guard $ consistant ruleBasedSolverResult
        if solved ruleBasedSolverResult
                then return ruleBasedSolverResult
                else speculate ruleBasedSolverResult

speculate :: Game -> [Game]
speculate game = do
        coord <- coords game
        guard $ lightableUnlit game coord
        let solutions = solve $ light game coord
        if null solutions
                then solve $ exclude game coord
                else solutions

Basically it applies some basic deterministic rules to see if that solves it. If not it tries putting lights in various places. If a light makes a puzzle inconsistent after recursing to solve it places an exclusion mark where the light was and proceeds. If it finds solutions while placing lights then it adds those to the list of solutions.

This works fine, but is slow because often there is an obvious choice for which coord to speculate on that will quickly result in an inconsistent puzzle and let you place an x with just one (or two) levels of search, but if it doesn't choose that coord until halfway through the search then it winds up chewing a bunch of uninteresting stuff first. Thus the breadth-first search idea.

I have googled things like "breadth first nondetermanism monad" and I get a few results that are difficult for me to understand, like:

  • Control.Monad.Omega This seems like overkill for what I need because it seems to guard against infinitely diverging determinism which is not the case for me, and I don't completely understand it.

  • Control.Monad.WeightedSearch the docs for Control.Monad.Omega suggest using this instead when using it as a Monad, but I think weighting is also a bit overkill for my needs. I would likely just have 2 weights, one for things with solutions and one for things that don't have solutions.

  • Control.Monad.Level I don't believe this will work for what I want since only the leaves of the tree have values.

  • Data.Tree I think this might be what I want to use, but I'm not sure how to convert my List monadic code to use it, though I feel like there's a beautiful way.

My next question will be about how to parallelize it :)

Jake Brownson
  • 469
  • 2
  • 9

1 Answers1

6

I believe that "Backtracking, Interleaving, and Terminating Monad Transformers" (Functional Pearl) by Kiselyov, Shan and Friedman outlines a solution.

Disclaimer: I am not an expert in this work!

Basically, you have to use a different monad. Since the ListT monad transformer does depth-first, they came up with a new monad transformer LogicT which does breadth-first. (If you're not interested in monad transformers, you can just apply a transformer to Id to get back a regular monad).

First, they recognize deficiencies in other approaches:

the straightforward depth-first search performed by most implementations of MonadPlus is not fair: a non-deterministic choice between two alternatives tries every solution from the first alternative before any solution from the second alternative. When the first alternative offers an infinite number of solutions, the second alternative is never tried, making the search incomplete. Indeed, as our examples in Section 3 show, fair backtracking helps more logic programs terminate.

[...]

The second deficiency in many existing backtracking monads is the adoption of Prolog’s cut, which confounds negation with pruning. Theoretically speaking, each of negation and pruning independently makes logic programming languages more expressive

[...]

The third practical deficiency is the often-forgotten top-level interface: how to run and interact with a computation that may return an infinite number of answers? The most common solution is to provide a stream that can be consumed or processed at the top- level as desired. But in the case of monad transformers, this solution only works if the base monad is non-strict (such as Haskell’s lazy list monad and LazyST). In the case where the base monad is strict, the evaluation may diverge by forcing the evaluation of the entire stream, even if we only desire one answer.

They then present a solution based on the LogicT monad transformer and the msplit function. Although the link to the code is broken, I searched Hoogle for LogicT and found this.

I hope that reading this paper will give you a good background in this topic and help you to understand how to use the projects that you already found.

If you find this paper useful, don't forget to check out its references and the other papers that cite it!

Community
  • 1
  • 1
Matt Fenwick
  • 48,199
  • 22
  • 128
  • 192
  • Thanks for the pointer. I came across that paper too in my searching, but among the many fairly dense materials I found I wasn't sure which to focus on. I'll look at that one more closely. – Jake Brownson Mar 11 '14 at 19:21
  • 1
    @JakeBrownson yes, it definitely takes some work to read it. Sorry I don't have a more concise code snippet. Good luck! – Matt Fenwick Mar 11 '14 at 19:28
  • 1
    @JakeBrownson I just found the code from Hoogle -- http://hackage.haskell.org/package/logict – Matt Fenwick Mar 11 '14 at 20:08
  • I started reading the paper and while looking up some stuff I didn't understand I stumbled on Control.Monad.Stream. It actually looks like this might be a better match? – Jake Brownson Mar 12 '14 at 02:46
  • Oh, I just noticed that monad-stream depends on logict, interesting. – Jake Brownson Mar 12 '14 at 02:57
  • 2
    [Here's](http://code.haskell.org/~byorgey/TMR/Issue15/Issue15.pdf#page=11) a paper that explains the `Logic` monad a bit more "lightweigth", IMHO. – phipsgabler Mar 12 '14 at 14:13
  • 1
    Okay I did a bunch of reading of various things and I am starting to get the ideas behind logict. I've swapped a few different monads in to my code, C.M.Logic, C.M.Stream, C.M.Omega, but they all still seem to be getting stuck on a deep search of one part of a puzzle instead of doing a more shallow evaluation. I get the feeling that their fair searches aren't fair enough, or perhaps I'm not using things like C.M.Stream.suspended properly. At the moment I've just tried sprinkling it in various places w/o really understanding it. I'll keep reading :). – Jake Brownson Mar 12 '14 at 15:54
  • I've been doing more research and I think all of the logic stuff isn't really what I want. They're not actually all that fair. They try to make sure they are fair enough that if you give them an infinite list as the first parameter to `mplus` they don't get stuck on it forever, but they're not designed for what I'm trying to do. – Jake Brownson Mar 12 '14 at 22:41
  • I also tried Control.Monad.Levels, but I finally realized that it's based on a binary tree and if you return a long list of possible results they aren't treated as being at the same level, but going deeper and deeper. I also looked more closely at Data.Tree, but to the contrary of what I said in the original Q I actually just want the leaves to have values, not the nodes. I think I'm looking for a C.M.Levels, but with arbitrary branching (non-binary), or in other words a Data.Tree but without data on the nodes. – Jake Brownson Mar 12 '14 at 22:44
  • ... except that it isn't a MonadPlus though I have a feeling it could be made one... I'm going to stop abusing this comment section and probably form a more specific question with my progress when I figure out exactly what it is. – Jake Brownson Mar 12 '14 at 23:11