Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

d

ambiguity

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.

like image 652
fadedbee Avatar asked Aug 31 '10 12:08

fadedbee


1 Answers

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.

like image 172
Peter Alexander Avatar answered Nov 08 '22 10:11

Peter Alexander