Amb() in JavaScript

You might have heard of the mysterious “ambiguous operator”. Just for fun, I wrote an implementation of it in JavaScript. It might even be useful. ;-)

So what is amb? Given a list of values, amb nondeterministically returns one of them in such a way that the rest of the program succeeds. When it is possible, of course. If it's not possible, then the program fails.

Say what? So it picks one value from a list, such that the rest of the program is successful!

Here is a hypothetical use for this operator:

var a = amb([1,3,5]);var b = amb([7,8,9]);if(a + b <=10)
fail();// amb will return in such way that the fail() line is never reached
console.log(a, b);// 3 and 8, or maybe 5 and 9, etc.

The exact usage will actually be a bit different in JavaScript, but the above is the way you could write it in Scheme because Scheme has first class continuations. A continuation is an "abstraction of the rest of the program" — so amb is able to “test” the rest of the program multiple times, giving it each value from the list, until it succeeds. I find this really cool.

I've seen a nice implementation for Common Lisp at Rosetta Code, even though Common Lisp doesn't have first-class continuations. With macros it can be made to look almost as a part of the language. That inspired me to try the JavaScript version. Following there are some examples of usage, and at the bottom of this article you can find the full amb code.

Examples

Find adjacent strings

This is the example used at Rosetta Code. Given a few lists of strings, find a sequence of strings (one from each list) such that they are adjacent (meaning, last char of a string is the first char of the next one). Here is my implementation in JS:

Some explanations here. First, note that we have two sections—which I'm calling the “preamble”, and “main program”. I couldn't figure out a different possible implementation in JavaScript. Basically amb needs to know which program to “try”. In Scheme it can get that easily via call/cc, but that's not possible in JS.

(in fact, our implementation is similar to the Common Lisp one: one can only use amb in the initialization part of amblet).

So, the function that we pass to amb_run receives two arguments, amb and fail, and does two things:

places calls to amb in the preamble (and only in the preamble!), and

returns a function which tests various conditions for the arguments given by amb and calls fail if the values aren't good.

Note that in the preamble I wrote “var w1 = amb(...)” etc. That wasn't really necessary, and as you can see, that variable is not used anywhere (in the main function it's shadowed). In fact, amb() doesn't return a meaningful value. I wrote it like this just for clarity.

Each call to amb in the preamble arranges for a new argument to be passed to the main function. So for w1 we will get, in order, one of "the", "that", "a". For w2 we get "frog", "elephant", "thing". Etc.

If the main program calls fail, the first argument is “incremented”. If it had all possible values, it restarts from first value and we move to “increment” the second argument. And so on. The main function is called for all possible combinations of values that amb gets to chose from.

Most problems that can be solved with backtracking can be conveniently expressed in terms of amb. Of course, there are probably faster/better ways to solve these problems; note that I don't care about speed at all here. Just showing how amb might be used.

One thing to note is is that we call fail() even when we got a solution. amb won't mind and will move on to find the next solution, therefore calling fail on each iteration will make sure you get all solutions. Here is a more versatile function based on amb that returns permutations of elements of an arbitrary array:

This is a bit more pragmatic. It uses a for loop to construct an array of integers [ 0, 1, 2, ..., n ] — where n is the number of elements that we want to permute. Instead of working with elements from the original array (which could be any objects, thus we'd have to implement special logic for ensuring their uniqueness) we will permute this list of integers, which are indexes into the original elements array. Once a solution is found, we map those indexes on the original array, thus fetching permuted original elements instead; then push this solution to PERM (so we can return it at the end) and fail(), in order to continue finding other permutations.

Also note that instead of getting arguments, the main function uses the this keyword. This is passed by amb and it's an array containing the arguments. If you want, it's a shortcut for arguments (but better, as it's a real array).

You can notice it uses a has_duplicate helper function, which I'll paste below. It's only safe when called with a list of strings or numbers (but that suffices for our implementation of permutations).

Pretty much like the last permutations function, this one sets up an array of numbers [ 1, 2, ..., N ] (where N is the board size). Then calls amb for that array N times. Then the main function iterates through this (which again contains the values picked up by amb) and tests that no two are equal, or attack in diagonal (the condition for this is Math.abs(a - b) == j - i — says that if the difference between the rows is the same as the difference between the cols, then those queens are attacking each other).

You might have noticed that in this example, as well as in the permutations one, we call amb with two values — the array to pick from and an index. When we do that we request that values come starting with the one at that index. It's convenient because otherwise we would start with [ 1, 1, 1, 1 ], [ 1, 1, 1, 2 ] etc. — they obviously don't make sense because we need them unique — so by passing the second argument to amb we make sure that the first arguments that the main function will get are like [ 1, 2, 3, 4 ].

Map coloring

This problem seems more complicated, but it's not. It's just that the input data is larger. We want to assign colors to 10 countries such that no two neighboring countries use the same color. We start by defining an array of countries—so we can use some IDs for each of them—then the array of neighbors has the elements of the form [ main_country, neighbor1, neighbor2, ... ] (ids of countries, returned by cix). I used the same countries as in Dorai Sitaram's book.

Update

Aliaksey Kandratsenka posted a gist with a better version than my original implementation. This one requires less boilerplate and it's more flexible, in that the values that amb gets to pick from can depend on values returned by a previous amb. The examples would need to be rewritten though, but for the better. :-) I edited it a bit, and here goes:

Not only is the code cleaner, but it's also faster because we avoid looking into unproductive cases: since we can check partial permutations for correctness, the main program is entered 21 times, instead of 27.

I implemented AMB using trampolines. A couple differences from your implementation... the API is only one function, amb, and the nesting of multiple choices / permutations is a bit more clear.See http://patricklogan.blogspot.com/2011/04/imp…

Trampolines are nice, but they introduce boilerplate (having to place return-s everywhere instead of just throwing out). For example the "iff" in "who owns the fish" takes two conditions and fails if they're not either both true or both false. Then it's called a dozen times to define the conditions of the problem. Had I used trampolines, the code would need to change to something like: