jll63 / openmethods.d

Open multi-methods for the D language. OPEN! Multi is cool. Open is great.
44 stars 8 forks source link

Comparison of openmethods vs. go interface/rust traits in readme.md #17

Closed jmh530 closed 5 years ago

jmh530 commented 5 years ago

There is a line in the readme.md about how open multi-methods are a feature that is brought over from some languages like Lisp:

"This library neatly solves this problem. It brings you the flexibility and the power of open multi-methods, as found in languages like Lisp, Closure, Dylan or TADS."

You might also add a line or two contrasting open methods with go interfaces and scala/rust traits*. As far as I can tell, their approach is very similar on the open side of things, but does not support multiple dispatch. There are a few other differences I can think of (though I imagine more so in terms of implementation): 1) rust/go do not require specifically inheriting from the interface/trait whereas open methods does, 2) rust/go require the function signatures that need to be implemented by the trait/interface to be specified there, open methods does not, 3) go interfaces are always dynamic, rust traits can be used statically or dynamically, as far as I can tell open methods are always dynamic dispatch.

*I had a point about Rust traits on your initial D forum announce thread. My above comments are from playing around with implementing them in D myself and realizing again that it's a very very similar concept.

jll63 commented 5 years ago

Thanks for the suggestion.

Indeed open methods are always dynamic.

I believe that Go-style interfaces (which, in my view, implement duck typing in a type safe manner) can be implemented in D very efficiently. I toyed a bit with this. How far did you go?

jmh530 commented 5 years ago

@jll63 Currently just a proof of concept below. I had done a few different iterations of varying complexity, but I kind of like this version most. It still needs a lot of stuff automatically generated. I was working on this when I realized how similar it was to what you had already done here. The key benefit is having the dynamic dispatch being opt-in. I also conceived of it as working with structs and classes (latest testing was with structs, probably wouldn't take much work to ensure it's also working for classes). https://run.dlang.io/gist/7b01f3b490a13d42a6301336ed35c2aa

jll63 commented 5 years ago

Aaaah, 404: Not Found

jmh530 commented 5 years ago

Weird

module traits;

import std.stdio : writeln;
import core.stdc.stdlib : malloc;
import std.experimental.allocator : theAllocator, make;
import std.traits : ReturnType, isPointer;

struct trait {}
struct impl {}

//used to generate implementations of functions for calling 
template GenImplFuncwImplType(string Trait, string Impl, string Impl_type)
{
    static if (is(ReturnType!(Trait.Impl) == void)) {
        const char[] GenImplFuncwImplType = 
            "ReturnType!(" ~ Trait ~ "." ~ Impl ~ ") " ~ Impl ~ "_" ~ Impl_type ~ "(" ~ Impl_type ~ "* x) { " ~ Impl ~ "(*x); }";
        }
    else {
        const char[] GenImplFuncwImplType = 
            "ReturnType!(" ~ Trait ~ "." ~ Impl ~ ") " ~ Impl ~ "_" ~ Impl_type ~ "(" ~ Impl_type ~ "* x) { return " ~ Impl ~ "(*x); }";
    }
}

mixin(GenImplFuncwImplType!("Geometry", "area", "Rect"));
mixin(GenImplFuncwImplType!("Geometry", "area", "Circle"));
mixin(GenImplFuncwImplType!("Geometry", "perim", "Rect"));
mixin(GenImplFuncwImplType!("Geometry", "perim", "Circle"));

//need to adjust this to handle all functions of TraitType
struct VTable(TraitType)
{
    float function(void*) area;
    float function(void*) perim;
}

struct traitObject(TraitType)
{
    void* data;
    VTable!(TraitType)* vtable;
    //originally tried to do with opDispatch, but it gets shadowed
}

//need to automate
static VTable!(Geometry) VTableGeometryRect = VTable!(Geometry)(cast(float function(void*))&area_Rect, cast(float function(void*))&perim_Rect);
static VTable!(Geometry) VTableGeometryCircle = VTable!(Geometry)(cast(float function(void*))&area_Circle, cast(float function(void*))&perim_Circle);

//handles pointers
auto passTrait(TraitType, T)(T* x)
{
    return mixin("traitObject!(TraitType)(x, &VTable" ~ TraitType.stringof ~ T.stringof ~ ")");
}
//handles structs
auto passTrait(TraitType, T)(return ref T x)
    if(!isPointer!T)
{
    return mixin("traitObject!(TraitType)(&x, &VTable" ~ TraitType.stringof ~ T.stringof ~ ")");
}

//need to generate automatically, opDispatch not working easily
float area(traitObject!(Geometry) x)
{
    return x.vtable.area(x.data);
}

float perim(traitObject!(Geometry) x)
{
    return x.vtable.perim(x.data);
}

struct Rect
{
    float width;
    float length;
}

struct Circle
{
    float radius;
}

@trait interface Geometry
{
    float area();
    float perim();
}

@impl float area(Rect r1)
{
    return r1.width * r1.length;
}

@impl float area(Circle c1)
{
    import std.math : pow, PI;

    return c1.radius.pow(2) * PI;
}

@impl float perim(Rect r1)
{
    return 2 * r1.width + 2 * r1.length;
}

@impl float perim(Circle c1)
{
    import std.math : pow, PI;
    return 2 * c1.radius * PI;
}

void measure(traitObject!Geometry value)
{
    writeln(value.area);
    writeln(value.perim);
}

void main()
{
    import std.math : PI, approxEqual;

    auto rect_stack = Rect(2f, 3f);
    assert(rect_stack.area == 6f);
    assert(rect_stack.perim == 10f);
    assert(rect_stack.passTrait!Geometry.area == 6f);
    assert(rect_stack.passTrait!Geometry.perim == 10f);
    rect_stack.passTrait!Geometry.measure;

    auto circle_stack = Circle(1f);
    assert(approxEqual(circle_stack.area, PI));
    assert(approxEqual(circle_stack.perim, 2f * PI));
    assert(approxEqual(circle_stack.passTrait!Geometry.area, PI));
    assert(approxEqual(circle_stack.passTrait!Geometry.perim, 2f * PI));
    circle_stack.passTrait!Geometry.measure;

    auto rect_heap = theAllocator.make!Rect(2f, 3f);
    assert(rect_heap.passTrait!Geometry.area == 6f);
    assert(rect_heap.passTrait!Geometry.perim == 10f);
    rect_heap.passTrait!Geometry.measure;

    auto circle_heap = theAllocator.make!Circle(1f);
    assert(approxEqual(circle_heap.passTrait!Geometry.area, PI));
    assert(approxEqual(circle_heap.passTrait!Geometry.perim, 2f * PI));
    circle_heap.passTrait!Geometry.measure;
}
jmh530 commented 5 years ago

@jll63 Did they prove to be useful at all?

jll63 commented 5 years ago

Did they prove to be useful at all?

What do you mean? Open methods?

jmh530 commented 5 years ago

No, the code I posted.

On Fri, Jan 18, 2019 at 1:52 PM Jean-Louis Leroy notifications@github.com wrote:

Did they prove to be useful at all?

What do you mean? Open methods?

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/jll63/openmethods.d/issues/17#issuecomment-455683999, or mute the thread https://github.com/notifications/unsubscribe-auth/AMJWygjpGDtgVHzApTFtbzxL1F8OXf9Qks5vEjQpgaJpZM4ZOGfR .

jll63 commented 5 years ago

Sorry I did not have much time to work in D recently. Closing this as this is not an issue. Let's keep in touch via email, how's your project going?