Closed jmh530 closed 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?
@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
Aaaah, 404: Not Found
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;
}
@jll63 Did they prove to be useful at all?
Did they prove to be useful at all?
What do you mean? Open methods?
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 .
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?
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.