digitalmars.D.learn - Compile Time Fun Time
- Yevano (33/33) Feb 24 2019 I am writing a domain specific language of sorts in D for the
- Nicholas Wilson (12/45) Feb 24 2019 import std.traits;
- Yevano (6/17) Feb 24 2019 Unfortunately this does not work, because f has untyped
- Simen =?UTF-8?B?S2rDpnLDpXM=?= (26/31) Feb 24 2019 The simple scalable version - just change maxArgs to a number
- Yevano (2/4) Feb 25 2019 It works! Thanks. Didn't know about static foreach.
- Yevano (6/6) Feb 25 2019 One thing. The variables were reversed. Fixed by changing these
- Simen =?UTF-8?B?S2rDpnLDpXM=?= (40/46) Feb 25 2019 Yup. I assumed that didn't matter, but was apparently wrong.
- Paul Backus (13/19) Feb 25 2019 A word of caution: this kind of thing looks cute, but
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 2019
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 2019
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 2019
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 2019
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 2019
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 2019
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 2019
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 2019