[bolts] bolts.functioneditor - module for building function mixins?

Jean-Louis Leroy jl at leroy.nyc
Tue Apr 7 16:58:12 UTC 2020


On Tuesday, 7 April 2020 at 11:50:10 UTC, aliak wrote:
> Am I correct in assuming that FunctionEditor is a way to 
> "stringify" various attributes of a function, in order to 
> create string versions of new functions, in order to mixin new 
> functions?

Yes. In a first step the function is "meta-objectified", 
capturing all the function attributes (well not UDAs yet), 
parameter storage classes, and whether it is a member or a static 
function, in a template instantiation which has:
- accessors for querying all the attributes from a centralized 
place
- "mutators" that create new function meta-objects with altered 
attributes
- accessors that return strings suitable to be mixed in to 
produce function declarations or definitions (and I am going to 
add more for making a function pointer, a delegate, and maybe for 
adding template parameters)

> Where being able to mock interfaces is one usecase?

Yes.

> What other usecases would there be?

Any situation that involves wrapping a function from a template, 
across module boundaries - keeping in mind what Adam Ruppe 
explains here: 
https://stackoverflow.com/questions/32615733/struct-composition-with-mixin-and-templates/32621854#32621854 (and that I painfully re-discovered by myself).

For example, my new ducktyping library works with two templates: 
InterfaceVariable(Interface), a fat pointer that contains a void* 
to an object and a Vtbl* to a table of forwarder functions; and 
as(T, IV), that returns an InterfaceVariable and builds the 
forwarders and Vtbl for type T.

Thus:

   interface Geometry
   {
     @nogc nothrow void move(real dx, real dy);
   }

   alias IGeometry = InterfaceValue!Geometry;

   class Rectangle
   {
     @nogc nothrow void move(real dx, real dy) { ... }
   }

   IGeometry geo = new Rectangle(...).as!IGeometry;

   geo.move(dx, dy);

...translates (more or less) to:

   struct InterfaceVariable!Geometry
   {
     struct Vtbl
     {
       @nogc nothrow void function(void* obj, real a0, real a1) 
move0;
     }
     void* obj;
     Vtbl* vtbl;
     @nogc nothrow void move(real dx, real dy) { vtbl.move0(obj, 
a0, a1); }
   }

   InterfaceVariable!Geometry as!(Rectangle, 
InterfaceVariable!Geometry)(Rectangle r)
   {
     static @nogc nothrow void move(void* obj, real a0, real a1)
     {
       (cast(Rectangle) obj).move(a0, a1);
     }
     static InterfaceVariable(Geometry).Vtbl vtbl = { &move0 };
     return InterfaceVariable(Geometry)(cast(void*) r, &vtbl);
   }

Note that it is essential to preserve `@nogc nothrow` (and any 
parameter storage classes even if the example doesn't show this).

> I looked at the real usage you have, but I don't think I 
> understood the dispatcher and discriminator

Given e.g.:

   module matrix;
   Matrix times(double a, virtual!Matrix b);
   @method DiagonalMatrix _times(double a, DiagonalMatrix b) { ... 
}

The library generates the following code:

   // in module matrix
   alias times = Method!(matrix, "times", 0).dispatcher;
   alias times = Method!(matrix, "times", 0).discriminator;

   // in Method!(matrix, "times", 0):
   double dispatcher(double a0, Matrix a1) { return 
resolve(a1)(a0, a1); }
   Method!(matrix, "times", 0) discriminator(MethodTag, double a0, 
Matrix a1);

   // also a pointer type:
   double function(double, Matrix) Spec;

   // and a code string to build a wrapper for a method 
specialization
   // it will be mixed in a context where 'Spec' is an alias to a 
specialization
   enum wrapper(Spec) = q{
     Matrix wrapper(double a0, Matrix a1) { return Spec(a0, 
cast(DiagonalMatrix) a1); }

Of course template Method is not allowed to use names like Matrix 
or DiagonalMatrix. Instead everything is channeled via what I 
call the "anchor", in this case essentially 
`__traits(getOverloads, Module, Name)[Index]`.

> If string interpolation was around would the interface you've 
> designed be the same?

Yes. The whole point is to avoid manipulating string until the 
very end - when specifying the body. And indeed, at that point, 
interpolation will help the *client* code a lot.

> I opened an issue in github as well: 
> https://github.com/aliak00/bolts/issues/9 just incase it makes 
> more sense to discuss it there.

Let's discuss here for the time being, as others may jump in with 
useful comments and suggestions.

 From the GH "issue":

> For structs and classes I may have had a prototype that went 
> something like:

I haven't given a lot of thought to using this approach beyond 
functions. Probably manipulating enums and classes are less of a 
pain (no parameter storage classes), and may need much fewer 
string mixins.

However, I do like the idea of composing e.g. enums using a 
meta-objects, like:

   
mixin(EnumEditor!"Fruit".addEnumerator!"Grapefruit".etc.etc.asString);

On a side note, I would like to have a very simple construct that 
turns an identifier into a string (see 
https://forum.dlang.org/post/ayzrsajzcelavxdbwbji@forum.dlang.org).

Finally I am not set on the template and module names. 
`FunctionEditor` is a bit errr flat. Transmogrify sounds much 
better ;-)


More information about the Digitalmars-d mailing list