www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Compile Time Fun Time

reply Yevano <jamking1996 gmail.com> writes:
I am writing a domain specific language of sorts in D for the 
lambda calculus. One of my requirements is that I should be able 
to generate expressions like this:

new Abstraction(v1, M)

like this:

L!(x => M)

It is common to want to write things like

L!(x => L!(y => M))

but it is much nicer to write that like

L!((x, y) => M)

So, I have created a templated function for this.

Abstraction L(alias f)() {
     static if(__traits(compiles, f(null))) {
         auto v1 = new Variable;
         return new Abstraction(v1, f(v1));
     } else static if(__traits(compiles, f(null, null))) {
         auto v1 = new Variable;
         auto v2 = new Variable;
         return new Abstraction(v1, new Abstraction(v2, f(v1, 
v2)));
     } else static if(__traits(compiles, f(null, null, null))) {
         auto v1 = new Variable;
         auto v2 = new Variable;
         auto v3 = new Variable;
         return new Abstraction(v1, new Abstraction(v2, new 
Abstraction(v3, f(v1, v2, v3))));
     }
}

This only works for at most 3 parameter delegates. If I want to 
add more, I have to linearly add more static ifs in the obvious 
way. However, I believe I can make this function scalable using 
string mixins and other magic. Any insight into this is much 
appreciated.
Feb 24
next sibling parent reply Nicholas Wilson <iamthewilsonator hotmail.com> writes:
On Monday, 25 February 2019 at 06:51:20 UTC, Yevano wrote:
 I am writing a domain specific language of sorts in D for the 
 lambda calculus. One of my requirements is that I should be 
 able to generate expressions like this:

 new Abstraction(v1, M)

 like this:

 L!(x => M)

 It is common to want to write things like

 L!(x => L!(y => M))

 but it is much nicer to write that like

 L!((x, y) => M)

 So, I have created a templated function for this.

 Abstraction L(alias f)() {
     static if(__traits(compiles, f(null))) {
         auto v1 = new Variable;
         return new Abstraction(v1, f(v1));
     } else static if(__traits(compiles, f(null, null))) {
         auto v1 = new Variable;
         auto v2 = new Variable;
         return new Abstraction(v1, new Abstraction(v2, f(v1, 
 v2)));
     } else static if(__traits(compiles, f(null, null, null))) {
         auto v1 = new Variable;
         auto v2 = new Variable;
         auto v3 = new Variable;
         return new Abstraction(v1, new Abstraction(v2, new 
 Abstraction(v3, f(v1, v2, v3))));
     }
 }

 This only works for at most 3 parameter delegates. If I want to 
 add more, I have to linearly add more static ifs in the obvious 
 way. However, I believe I can make this function scalable using 
 string mixins and other magic. Any insight into this is much 
 appreciated.
import std.traits; Abstraction L(alias f)() { alias Args = Parameters!f; Args v; foreach(i; 0 .. v.length) v[i] = new Variable; auto _f = f(v); auto abstraction = new Abstraction(v[$-1],_f); foreach_reverse(e; v[ 0 .. $-2]) abstraction = new Abstraction( e, abstraction); return abstraction; }
Feb 24
parent Yevano <jamking1996 gmail.com> writes:
On Monday, 25 February 2019 at 07:03:21 UTC, Nicholas Wilson 
wrote:
 import std.traits;
 Abstraction L(alias f)() {
      alias Args = Parameters!f;
      Args v;
      foreach(i; 0 .. v.length) v[i] =  new Variable;
      auto _f = f(v);
      auto abstraction = new Abstraction(v[$-1],_f);
      foreach_reverse(e; v[ 0 .. $-2])
           abstraction =  new Abstraction( e, abstraction);
      return abstraction;
 }
Unfortunately this does not work, because f has untyped arguments, and therefore is passed as some kind of pseudo-template with type void. In other words, Parameters!f fails, because f is technically not a function.
Feb 24
prev sibling next sibling parent reply Simen =?UTF-8?B?S2rDpnLDpXM=?= <simen.kjaras gmail.com> writes:
On Monday, 25 February 2019 at 06:51:20 UTC, Yevano wrote:
 This only works for at most 3 parameter delegates. If I want to 
 add more, I have to linearly add more static ifs in the obvious 
 way. However, I believe I can make this function scalable using 
 string mixins and other magic. Any insight into this is much 
 appreciated.
The simple scalable version - just change maxArgs to a number that suits you: Abstraction L(alias f)() { import std.meta : Repeat; enum maxArgs = 20; static foreach (i; 0..maxArgs) { static if (__traits(compiles, f(Repeat!(i, null)))) { Repeat!(i, Variable) vars; foreach (ref e; vars) { e = new Variable(); } auto result = new Abstraction(vars[0], f(vars)); foreach (e; vars[1..$]) { result = new Abstraction(e, result); } return result; } } } The problem with lambdas is they only present an opaque name when inspected with .stringof, so you can't easily check the arity. For regular templated functions it can be done, but requires parsing some substantial subset of D code. -- Simen
Feb 24
parent reply Yevano <jamking1996 gmail.com> writes:
On Monday, 25 February 2019 at 07:40:51 UTC, Simen Kjærås wrote:
 The simple scalable version - just change maxArgs to a number 
 that suits you:
It works! Thanks. Didn't know about static foreach.
Feb 25
parent reply Yevano <jamking1996 gmail.com> writes:
One thing. The variables were reversed. Fixed by changing these 
lines.

auto result = new Abstraction(vars[$ - 1], f(vars));

foreach_reverse(e; vars[0..$ - 1]) {
     result = new Abstraction(e, result);
}
Feb 25
parent Simen =?UTF-8?B?S2rDpnLDpXM=?= <simen.kjaras gmail.com> writes:
On Monday, 25 February 2019 at 08:24:06 UTC, Yevano wrote:
 One thing. The variables were reversed. Fixed by changing these 
 lines.

 auto result = new Abstraction(vars[$ - 1], f(vars));

 foreach_reverse(e; vars[0..$ - 1]) {
     result = new Abstraction(e, result);
 }
Yup. I assumed that didn't matter, but was apparently wrong. One other thing - the static foreach should read 1.. instead of 0.., since it should fail on nilary lambdas. You could also argue it should give better error messages when you do things like L!((int n) => n), pass variadic templated functions to it, or just plain give it something other than a lambda. It might also benefit from checking __traits(compiles, f(Repeat!(i, Variable.init))), as typeof(null) is valid but might not behave the way you expect a Variable to do. Here's a version with these issues amended: Abstraction L(alias f)() { import std.meta : Repeat; enum maxArgs = 20; static foreach (i; 1..maxArgs) { static if (__traits(compiles, f(Repeat!(i, Variable.init)))) { // Check if there has already been a match static if (__traits(compiles, { vars[0] = null; })) { static assert(false, "Multiple matches in L."); } Repeat!(i, Variable) vars; // Initialize vars in opposite order foreach_reverse (ref e; vars) { e = new Variable(); } auto result = new Abstraction(vars[0], f(vars)); foreach (e; vars[1..$]) { result = new Abstraction(e, result); } return result; } } // Check if there have been any matches static if (!__traits(compiles, { vars[0] = null; })) { static assert(false, "No matches in L."); } } -- Simen
Feb 25
prev sibling parent Paul Backus <snarwin gmail.com> writes:
On Monday, 25 February 2019 at 06:51:20 UTC, Yevano wrote:
 I am writing a domain specific language of sorts in D for the 
 lambda calculus. One of my requirements is that I should be 
 able to generate expressions like this:

 new Abstraction(v1, M)

 like this:

 L!(x => M)
A word of caution: this kind of thing looks cute, but fundamentally, you are using D's lambda syntax in a way it was never intended to be used. The more you try to build on top of this, the more you will find yourself fighting the language, and the more you will be forced to resort to ugly, brittle workarounds to make your DSL function. A much better approach is to write your DSL inside string literals, and parse it into proper data structures using CTFE. For example: L!"x.M" ...would be equivalent to something like: Abstraction(Variable("x"), M)
Feb 25