July 17, 2017
On 07/16/2017 10:24 AM, Jean-Louis Leroy wrote:
> TL;DR: see here https://github.com/jll63/methods.d/blob/master/README.md

Woot! :) I'm so happy to see this project complete.

Honestly, growing up with languages without this feature (C and C++), I've not even known that I needed this feature but your example (e.g. matrix multiplication) are very convincing.

If there are enough differences compared to your C++ articles, perhaps you may consider following this up with a blog post. It would be nice to see some performance results as well like you have on your C++ articles.

Ali

July 18, 2017
On Tuesday, 18 July 2017 at 02:22:15 UTC, Jay Norwood wrote:
> An excerpt statement from this wiki page is :
> "  dynamically dispatched based on the run-time (dynamic) type or, in the more general case some other attribute, of more than one of its arguments"
>
>
> Based on the 'some other attribute', I wonder if the library could conceivably be extended to dispatch based on the User Defined Attribute info
>
> https://dlang.org/spec/attribute.html
>
> @('c') string s;
> pragma(msg, __traits(getAttributes, s)); // prints tuple('c')

For example, CLOS allows you to specialize on a value (google for "eql specialize"). IIRC Clojure allows you to specify your own dispatcher.

As for specializing on D attributes, I don't think it's feasible. They are a purely compile-time mechanism. In your example, the type of "s" is "string", not "@('c') string".

J-L

July 18, 2017
On Tuesday, 18 July 2017 at 04:26:42 UTC, Ali Çehreli wrote:
> On 07/16/2017 10:24 AM, Jean-Louis Leroy wrote:
> > TL;DR: see here
> https://github.com/jll63/methods.d/blob/master/README.md
>
> Woot! :) I'm so happy to see this project complete.
>
> Honestly, growing up with languages without this feature (C and C++), I've not even known that I needed this feature but your example (e.g. matrix multiplication) are very convincing.

Thanks :) I added another example that shows how open methods are a superior alternative to Visitor: https://github.com/jll63/methods.d/blob/master/examples/novisitor/source/app.d

>
> If there are enough differences compared to your C++ articles, perhaps you may consider following this up with a blog post. It would be nice to see some performance results as well like you have on your C++ articles.

Yes I will probably write something. You mean on the D Blog?

As for performance, I have a first result: https://github.com/jll63/methods.d/blob/master/benchmarks/source/benchmarks.d#L122 but I still have to implement the "first argument optimization". I am working on it.

J-L




>
> Ali


July 18, 2017
On Tuesday, 18 July 2017 at 07:06:10 UTC, Jean-Louis Leroy wrote:
> As for performance, I have a first result: https://github.com/jll63/methods.d/blob/master/benchmarks/source/benchmarks.d#L122 but I still have to implement the "first argument optimization". I am working on it.

Now this is funny, after implementing that optimization (https://github.com/jll63/methods.d/blob/94ad5a945b3c719bd8f8402bb0aa6fda8e7a6be0/source/openmethods.d#L388, https://github.com/jll63/methods.d/blob/94ad5a945b3c719bd8f8402bb0aa6fda8e7a6be0/benchmarks/source/benchmarks.d#L139) it runs faster with ldc2 but slower with dmd. I may be testing the limits of dmd's willingness to inline my mess ;-)

J-L



July 18, 2017
On 07/18/2017 12:06 AM, Jean-Louis Leroy wrote:

> Yes I will probably write something. You mean on the D Blog?

Not necessarily but why not. :)

> As for performance, I have a first result:
> https://github.com/jll63/methods.d/blob/master/benchmarks/source/benchmarks.d#L122
> but I still have to implement the "first argument optimization". I am
> working on it.

I could use some explanation for the results but I can for the blog article. ;)

It's not surprising that ldc (and gdc) can be much better than dmd in optimization.

By the way, I'm in awe of your D skills in such a short time! I'm sure there are parts of the code that can be cleaned up but it's taking advantage of many powerful features of the language. I still think the usage can be made easier but I'm not sure yet. I hope others will take a look at the code and come up with an easier interface. Perhaps they are all needed but I'm thinking about the need for forward declaration, the need for the underscore prefix, etc.

Ali

July 18, 2017
On Tuesday, 18 July 2017 at 16:57:30 UTC, Ali Çehreli wrote:
>
> Perhaps they are all needed but I'm thinking about the need for forward declaration, the need for the underscore prefix, etc.
>

He might be able to at least get rid of the forward declaration (not sure on the underscore).

The way it works now is that a class that inherits from an interface is not required in any way to implement the methods. Suppose he adds another attribute to an interface such that any class that inherits from it is required to have methods defined for specific functions.

So for instance, the Matrix example might look something like

@trait
interface Matrix
{
  @property int rows() const;
  @property int cols() const;
  @property double at(int i, int j) const;
  @trait void print();
}

I'm not sure this would work because anything that derives from Matrix must implement print. However, if it is possible to use the attribute to allow the derived classes to ignore print, then it might work. Alternately, if there is a way to process the interface and tell the compiler to somehow ignore the @trait member functions. I don't know if it'll work, but it's an idea.

Anyway, the mixin(registerMethods); could then be adjusted so that void print(virtual!Matrix m); is mixed in automatically because we now know how to construct it.
July 18, 2017
On 07/18/2017 11:03 AM, jmh530 wrote:

> the mixin(registerMethods); could then be adjusted so that void
> print(virtual!Matrix m); is mixed in automatically because we now know
> how to construct it.

That reminds me: Would the following be possible and better?

// From
void main()
{
  updateMethods();
  // ...
}

// To
mixin(constructMethods());
void main()
{
  // ...
}

constructMethods() could return the following string:

string constructMethods() {
  return q{
    shared static this() { updateMethods(); }
  };
}

If I'm not missing something, this is better because nothing needs to be added to main and the methods are available before main starts executing (module initialization order issues still apply.).

Ali

July 18, 2017
On Tuesday, 18 July 2017 at 16:57:30 UTC, Ali Çehreli wrote:
> > As for performance, I have a first result:
> > 
> https://github.com/jll63/methods.d/blob/master/benchmarks/source/benchmarks.d#L122
> > but I still have to implement the "first argument
> optimization". I am
> > working on it.
>
> I could use some explanation for the results but I can for the blog article. ;)

I pit a method-based call against its equivalent using virtual functions. First calling a virtual function via a base class is pitted against a method with one virtual parameter. Then the same but calling via an interface. Lastly, I compare double dispatch with a method with two virtual arguments. I use std.datetime.comparingBenchmark, which reports the result as time(base)/time(target). So open methods are a bit slower than ordinary virtual function calls but not that much. In the meantime I have applied a second optimization for unary methods and this brings them within 33% of an ordinary, compiler implemented vfunc call. Which is OK because the situation is highly artificial. If the function does anything, the difference will be imperceptible.

I am more annoyed by double dispatch beating binary methods. I will have to look at the assembler, but it may be that the index pointer is too far from the object. To begin the real work, I need to fetch that pointer form an object. Currently it is stored in ClassInfo.deallocator, so I have to 1/ fetch the vptr 2/ fetch the ClassInfo* 3/ fetch 'deallocator'. What happens next depends on the arity.

Any chance of Walter giving me a pointer in the vtable? Aside the ClassInfo*? Or at least a pointer in ClassInfo, or reassign the deallocator when it is eventually retired?

> It's not surprising that ldc (and gdc) can be much better than dmd in optimization.

I would like to try gdc but it conflicts with ldc2 - you know, the "alias __va_list = __va_list_tag;" issue. I found suggestions via google but nothing worked for me so far.

>
> By the way, I'm in awe of your D skills in such a short time!

Thanks :) I found out that D was much more natural, "predictable" than C++. The most cryptic error messages happened when I forgot the "!", IIRC.

> I'm sure there are parts of the code that can be cleaned up but it's taking advantage of many powerful features of the language. I still think the usage can be made easier but I'm not sure yet. I hope others will take a look at the code and come up with an easier interface. Perhaps they are all needed but I'm thinking about the need for forward declaration, the need for the underscore prefix, etc.

(in reverse order)

Regarding the prefix, it is needed to prevent overload resolution from trumping dynamic dispatch - see here: https://github.com/jll63/methods.d/blob/master/examples/whytheunderscore/source/app.d Allowing the same name would necessitate compiler support.

As for the the forward declaration - I don't think it is possible to dispense with it. All open methods systems I know of have it (defgeneric in CLOS, defmulti in Clojure, Stroustrup's proposal...). Consider:

class A { }
class B : A { }
class X : B { }
class Y : B { }

@method void _foo(virtual!X x) { ... }
@method void _foo(virtual!Y x) { ... }

What is the base method? foo(B)? foo(A)? It may well be the latter. Also don't forget that the complete specialization set is known, at the earliest, at link time. If you (arbitrarily) pick foo(B), another module may introduce a B or an A specialization.

As for suggestions and advise, they are very welcome :) already got a couple of PRs. Here are the remaining questions on my mind:

- the module/package name: I am pretty much set on openmethods though...

- throw an exception if a method is not define for the argument set, or ambiguous: having big doubts about this. We want the possibility of @nothrow methods, don't we? So I will probably call a delegate via a pointer (a la C++) which, by default, will abort().

- the method prefix: hesitating between just _ or maybe m_ ???

- replace version(explain) with debug levels?
July 18, 2017
On Tuesday, 18 July 2017 at 18:21:21 UTC, Ali Çehreli wrote:
> That reminds me: Would the following be possible and better?
>
> // From
> void main()
> {
>   updateMethods();
>   // ...
> }
>
> // To
> mixin(constructMethods());
> void main()
> {
>   // ...
> }
>
> constructMethods() could return the following string:
>
> string constructMethods() {
>   return q{
>     shared static this() { updateMethods(); }
>   };
> }
>
> If I'm not missing something, this is better because nothing needs to be added to main and the methods are available before main starts executing (module initialization order issues still apply.).

Ah, I would love to get rid of that call in main(), but think beyond a one module program. The matrix example (https://github.com/jll63/methods.d/tree/master/examples/matrix/source) consists in three separate modules, plus an app, all defining specializations. They need the mixin, but if we put updateMehods() in there, it will be called many times. And it is a costly operation. Guarding the call with a flag will not work, because more methods may be registered afterwards. Unless you can think of a way the last mixin can detect it's the last?

Incidentally, in yomm11 that function (it's called initialize()) has to be called before any method is called, after any shared object/DLL is loaded and after a DLL is unloaded. I still have to write the code to de-register the methods and the specializations in that case by the way...

J-L

J-L
July 18, 2017
On Tuesday, 18 July 2017 at 18:03:30 UTC, jmh530 wrote:
> On Tuesday, 18 July 2017 at 16:57:30 UTC, Ali Çehreli wrote:
>>
>> Perhaps they are all needed but I'm thinking about the need for forward declaration, the need for the underscore prefix, etc.
>>
>
> He might be able to at least get rid of the forward declaration (not sure on the underscore).
>
> The way it works now is that a class that inherits from an interface is not required in any way to implement the methods. Suppose he adds another attribute to an interface such that any class that inherits from it is required to have methods defined for specific functions.
>
> So for instance, the Matrix example might look something like
>
> @trait
> interface Matrix
> {
>   @property int rows() const;
>   @property int cols() const;
>   @property double at(int i, int j) const;
>   @trait void print();
> }
>
> I'm not sure this would work because anything that derives from Matrix must implement print. However, if it is possible to use the attribute to allow the derived classes to ignore print, then it might work. Alternately, if there is a way to process the interface and tell the compiler to somehow ignore the @trait member functions. I don't know if it'll work, but it's an idea.
>
> Anyway, the mixin(registerMethods); could then be adjusted so that void print(virtual!Matrix m); is mixed in automatically because we now know how to construct it.

There are at least problems with this. Firstly it is intrusive - something I strive to avoid (although I could be 100% orthogonal only because I hijack a deprecated pointer in ClassInfo). Also, some methods may want to treat Matrix as a virtual argument, and some not.

Look at https://github.com/jll63/methods.d/blob/master/examples/matrix/source/matrix.d and https://github.com/jll63/methods.d/blob/master/examples/matrix/source/densematrix.d They know nothing about printing. They don't want to. The matrix modules do math, the app does printing.

J-L