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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With