tags:

views:

207

answers:

1

Is it possible to generically implement the amb operator in D?

http://www.haskell.org/haskellwiki/Amb
http://www.randomhacks.net/articles/2005/10/11/amb-operator

The sort of thing I'm thinking of is:

amb([1, 2]) * amb([3, 4, 5]) == amb([3, 4, 5, 6, 8, 10])
amb(["hello", "world"]) ~ amb(["qwerty"]) == amb(["helloqwerty", "worldqwerty"])
amb(["hello", "world"]) ~ "qwerty" == amb(["helloqwerty", "worldqwerty"])
amb(["hello", "very long string"]).length = amb([5, 16])

In the last two examples, there really needs to be a 'lifting' of the ~ and .length into the amb 'context' (a monad?). In the first two examples, the operators should just be applied to the amb's contents.

I've given it a brief try, but I'm having problems when trying to lift the wrapped-type's operators/methods/properties (*, ~ and .length in this example). How should this be done in D?

Thanks,

Chris.

+8  A: 

Yes, it is possible. Here's what I came up with.

import std.range;
import std.algorithm;
import std.stdio;
import std.functional;
import std.math;
import std.string;

struct AmbRange(R1, R2, alias Op)
{
public:
    this(R1 _r1, R2 _r2) { r1 = _r1; r2 = r2c = _r2; }

    void popFront()
    {
        r2.popFront();
        if (r2.empty) { r2 = r2c; r1.popFront(); }
    }

    @property auto front() { return Op(r1.front, r2.front); }
    @property bool empty() { return r1.empty; }

private:
    R1 r1;
    R2 r2, r2c;
}

struct Amb(R)
{
    alias ElementType!(R) E;

public:
    this(R r) { this.r = r; }

    auto opBinary(string op, T)(T rhs) if (!is(T U : Amb!(U)))
    {
        alias binaryFun!("a"~op~"b") Op;
        return map!((E e) { return Op(e, rhs); })(r);
    }

    auto opBinaryRight(string op, T)(T lhs) if (!is(T U : Amb!(U)))
    {
        alias binaryFun!("a"~op~"b") Op;
        return map!((E e) { return Op(lhs, e); })(r);
    }

    auto opBinary(string op, T)(T rhs) if (is(T U : Amb!(U)))
    {
        alias binaryFun!("a"~op~"b") Op;
        return AmbRange!(R, typeof(rhs.r), Op)(r, rhs.r);
    }

    auto opDispatch(string f, T ...)(T args)
    {
        mixin("return map!((E e) { return e."~f~"(args); })(r);");
    }

    auto opDispatch(string f)()
    {
        mixin("return map!((E e) { return e."~f~"; })(r);");
    }

private:
    R r;
}

auto amb(R)(R r) { return Amb!R(r); }

void main()
{
    auto r1 = 2 * amb([1, 2, 3]);
    assert(equal(r1, [2, 4, 6]));

    auto r2 = amb(["ca", "ra"]) ~ "t";
    assert(equal(r2, ["cat", "rat"]));

    auto r3 = amb(["hello", "cat"]).length;
    assert(equal(r3, [5, 3]));

    auto r4 = amb(["cat", "pat"]).replace("a", "u");
    assert(equal(r4, ["cut", "put"]));

    auto r5 = amb([1, 2]) * amb([1, 2, 3]);
    assert(equal(r5, [1, 2, 3, 2, 4, 6]));
}

Lots of thanks to BCS for figuring out how to resolve the binaryOp ambiguities.

I had to create a new range for traversing the result of a binary op between two Amb's, but I think that works out best anyway.

For those that are new to D, and are curious, all that string stuff is done at compile time, so there's no parsing code at runtime or anything like that -- it's pretty much as efficient as hand-coding it in C.

Peter Alexander
take a look at using `auto opBinaryRight(string op, T)(T lhs) if(!is(T U : Amb!(U)))`
BCS
Thanks BCS, that did the trick :) I tried to do something like that before, but couldn't get the syntax right -- never thought to put the `T` `U` next to each other like that, only separated by space (I put a comma when I tried).
Peter Alexander
Thanks, that's an excellent piece of work, great for illustrating some compile-time features of D.Would you mind if I took this and extended it with (exception-based?) backtracking as a full amb operator? (I'll release under MIT/BSD on github, with full credit to you and BCS.)
chrisdew
Yeah sure, do what you like with it :) What do you mean by backtracking?
Peter Alexander
@chrisdew: if you do, post to the D newsgroup and you might be able to get it into the stdlib.
BCS