www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - As discussed in DConf2015: Python-like keyword arguments

reply "Atila Neves" <atila.neves gmail.com> writes:
I might do a blog post on this, but here's some POC code:

import std.stdio;
import std.range;
import std.typetuple;
import std.traits;
import std.conv;


struct Foo { int i; }
struct Bar { int i; }
struct Baz { int i; }


void func(Foo foo, Bar bar, Baz baz) {
     writeln("foo is ", foo);
     writeln("bar is ", bar);
     writeln("baz is ", baz);
}


auto getStrArgs(alias F, T...)() {
     string[] strArgs;

     foreach(i, ParamType; ParameterTypeTuple!F) {
         enum index = staticIndexOf!(ParamType, T);

         static if(index != -1) {
             strArgs ~= "args[" ~ index.to!string ~ "]";
         } else {
             strArgs ~= ParamType.stringof ~ ".init";
         }
     }

     return strArgs;
}

auto kwargs(alias F, T...)(T args) {
     enum strArgs = getStrArgs!(F, T);
     mixin("return F(" ~ strArgs.join(",") ~ ");");
}

void main() {
     kwargs!func(Bar(2), Baz(3), Foo(1));
     kwargs!func(Baz(3), Foo(1));
}
May 28 2015
next sibling parent reply Jacob Carlborg <doob me.com> writes:
On 2015-05-29 00:35, Atila Neves wrote:
 I might do a blog post on this, but here's some POC code:

 import std.stdio;
 import std.range;
 import std.typetuple;
 import std.traits;
 import std.conv;


 struct Foo { int i; }
 struct Bar { int i; }
 struct Baz { int i; }


 void func(Foo foo, Bar bar, Baz baz) {
      writeln("foo is ", foo);
      writeln("bar is ", bar);
      writeln("baz is ", baz);
 }


 auto getStrArgs(alias F, T...)() {
      string[] strArgs;

      foreach(i, ParamType; ParameterTypeTuple!F) {
          enum index = staticIndexOf!(ParamType, T);

          static if(index != -1) {
              strArgs ~= "args[" ~ index.to!string ~ "]";
          } else {
              strArgs ~= ParamType.stringof ~ ".init";
          }
      }

      return strArgs;
 }

 auto kwargs(alias F, T...)(T args) {
      enum strArgs = getStrArgs!(F, T);
      mixin("return F(" ~ strArgs.join(",") ~ ");");
 }

 void main() {
      kwargs!func(Bar(2), Baz(3), Foo(1));
      kwargs!func(Baz(3), Foo(1));
 }
Here's another solution [1]. And here's an implementation with language support which allows named arguments but not reordering the arguments [2]. Originally implemented by Michel Fortin. [1] https://github.com/jacob-carlborg/mambo/blob/master/mambo/util/Reflection.d#L135 [2] https://github.com/jacob-carlborg/dmd/tree/named_parameters -- /Jacob Carlborg
May 29 2015
next sibling parent "Michael Coulombe" <kirsybuu gmail.com> writes:
I know some people don't like abusing opDollar, but here's my 
implementation:


import std.traits, std.algorithm, std.conv;
private struct Named(string n, T) {
     enum name = n;
     T value;
}
private auto makeNameIndex(NamedList...)(string[] names) {
     auto indices = new size_t[](names.length);
     foreach(i, n ; NamedList) {
         auto x = names.countUntil(n.name);
         assert(x >= 0, "Keyword Parameter \"" ~ n.name ~ "\" does 
not exist in " ~ text(names));
         indices[x] = i;
     }
     return indices;
}
private immutable struct KeyWordDollar {
      property
     auto opDispatch(string param, T)(T t) {
         return Named!(param,T)(t);
     }
}
private struct KeyWordManager(alias f) {
     private enum paramNames = [ParameterIdentifierTuple!f];

     auto opDollar(size_t pos = 0)() const {
         return immutable KeyWordDollar();
     }
     auto opIndex(NamedArgs...)(NamedArgs namedArgs) {
         enum ordering = makeNameIndex!NamedArgs(paramNames);
         ParameterTypeTuple!f args;
         foreach(i, ref a; args) {
             a = namedArgs[ ordering[i] ].value;
         }
         return f(args);
     }
}
auto kw(alias f)() {
     return KeyWordManager!f();
}

int foo(int x, int y) {
     return x+y;
}
unittest {
     assert(8 == kw!foo[$.y = 5, $.x = 3]);
}
May 29 2015
prev sibling next sibling parent ketmar <ketmar ketmar.no-ip.org> writes:
On Fri, 29 May 2015 14:27:02 +0200, Jacob Carlborg wrote:

 [2] https://github.com/jacob-carlborg/dmd/tree/named_parameters
nice start. with some efforts i ported it as PoC to git HEAD, and added=20 argument reordering, so void foo (string a, string b); ... foo(a:"hello", b:"world"); foo(b:"world", a:"hello"); is working as one expects. nice addition to Aliced. ;-)=
May 29 2015
prev sibling next sibling parent ketmar <ketmar ketmar.no-ip.org> writes:
On Fri, 29 May 2015 14:27:02 +0200, Jacob Carlborg wrote:

i can do even more cool things like this now:

  void test (string a, string b=3D"wow", string c=3D"heh") {
    assert(b =3D=3D "wow");
  }
  test(c: "cc", a: "aa");

the long-wanting feature of "use defaults for some args". i like it.=
May 29 2015
prev sibling parent reply Michel Fortin <michel.fortin michelf.ca> writes:
On 2015-05-29 12:27:02 +0000, Jacob Carlborg <doob me.com> said:

 And here's an implementation with language support which allows named 
 arguments but not reordering the arguments [2]. Originally implemented 
 by Michel Fortin.
 
 [2] https://github.com/jacob-carlborg/dmd/tree/named_parameters
I didn't know you revived that thing too. Nice. Make sure you take note of the related comments between Walter and me here: https://github.com/michelf/dmd/commit/673bae4982ff18a3d216bc1578f50d40f4d26d7a At some point my plans about this changed and I wanted to implement named arguments differently so it'd work for template arguments too. But I never go to it. -- Michel Fortin michel.fortin michelf.ca http://michelf.ca
May 30 2015
next sibling parent reply ketmar <ketmar ketmar.no-ip.org> writes:
On Sat, 30 May 2015 21:59:53 -0400, Michel Fortin wrote:

 On 2015-05-29 12:27:02 +0000, Jacob Carlborg <doob me.com> said:
=20
 And here's an implementation with language support which allows named
 arguments but not reordering the arguments [2]. Originally implemented
 by Michel Fortin.
=20
 [2] https://github.com/jacob-carlborg/dmd/tree/named_parameters
=20 I didn't know you revived that thing too. Nice. =20 Make sure you take note of the related comments between Walter and me here: https://github.com/michelf/dmd/
commit/673bae4982ff18a3d216bc1578f50d40f4d26d7a
=20
 At some point my plans about this changed and I wanted to implement
 named arguments differently so it'd work for template arguments too.
 But I never go to it.
my work now allows this: string test (string a, string b=3D"wow", string c=3D"heh") { return a~b~c; } void main () { enum str =3D test(c: "cc", a: "aa"); assert(str =3D=3D "aawowcc"); } and this: void test(A...) (A a) { import std.stdio; foreach (auto t; a) writeln(t); } void main () { test(x: 33.3, z: 44.4, a: 9999, 7777, d:"Yehaw"); } but still not this: string test(T) (T a, string b=3D"wow", string c=3D"heh") { import std.conv : to; return to!string(a)~b~c; } void main () { enum str =3D test(c: "cc", a: 42); // can't //enum str =3D test(a: 42, c: "cc"); // WORKS assert(str =3D=3D "42wowcc"); } i have to add reorder checks to template resoultion code yet. also, no support for named "!" args. the patch is fairly simple and almost non-intrusive (one big function in=20 mtype.c and processing of NamedArgExp at different visitors). .di=20 generation is supported too.=
May 30 2015
next sibling parent "Idan Arye" <GenericNPC gmail.com> writes:
On Sunday, 31 May 2015 at 04:08:33 UTC, ketmar wrote:
 and this:
   void test(A...) (A a) {
     import std.stdio;
     foreach (auto t; a) writeln(t);
   }

   void main () {
     test(x: 33.3, z: 44.4, a: 9999, 7777, d:"Yehaw");
   }
I like the idea of a template-variadic keyword arguments, but does it have to have the exact same syntax as template-variadic positional arguments? What will happen to functions that expect positional variadic arguments and get invoked with keyword variadic arguments instead?
May 31 2015
prev sibling parent reply Michel Fortin <michel.fortin michelf.ca> writes:
On 2015-05-31 04:08:33 +0000, ketmar <ketmar ketmar.no-ip.org> said:

 my work now allows this:
   string test (string a, string b=3D"wow", string c=3D"heh") {
     return a~b~c;
   }
 
   void main () {
     enum str =3D test(c: "cc", a: "aa");
     assert(str =3D=3D "aawowcc");
   }
How does it handle overloading? string test(bool a, string b="wow", string c="heh") {} string test(bool a, string c="heh", bool d=true) {} test(a: true, c: "hi"); // ambiguous! The irony of this example is that without argument names (or more precisely without reordering), there'd be no ambiguity here.
 and this:
   void test(A...) (A a) {
     import std.stdio;
     foreach (auto t; a) writeln(t);
   }
 
   void main () {
     test(x: 33.3, z: 44.4, a: 9999, 7777, d:"Yehaw");
   }
For that to be really useful the argument names should be part of the "A" type so you can forward them to another function and it still works. For instance: void test(string a, string b="wow", string c="heh") {} void forward(A...)(A a) { test(a); } void main() { forward(c: "cc", a: "aa"); } -- Michel Fortin michel.fortin michelf.ca http://michelf.ca
May 31 2015
next sibling parent ketmar <ketmar ketmar.no-ip.org> writes:
On Sun, 31 May 2015 09:43:50 -0400, Michel Fortin wrote:

 How does it handle overloading?
=20
 	string test(bool a, string b=3D"wow", string c=3D"heh") {}
 	string test(bool a, string c=3D"heh", bool d=3Dtrue) {}
=20
 	test(a: true, c: "hi"); // ambiguous!
as for named calls argument order doesn't really matter (i.e. it tries to=20 reorder first, and then doing match), it can't resolve this.
 The irony of this example is that without argument names (or more
 precisely without reordering), there'd be no ambiguity here.
yes. but i'm not sure that i want such resolving. but it's fairly easy to=20 do: just try the original order first (as missing arg means "reordering"=20 too), and only if there were no hits, try to reorder. little more code, though, for a feature i'm not sure i want.
 and this:
   void test(A...) (A a) {
     import std.stdio;
     foreach (auto t; a) writeln(t);
   }
=20
   void main () {
     test(x: 33.3, z: 44.4, a: 9999, 7777, d:"Yehaw");
   }
=20 For that to be really useful the argument names should be part of the "A" type so you can forward them to another function and it still works.
yes, i'm eventually planning to do that. inside the frontend they are=20 NamedArgExp instances, so the information is here (althru they eventually=20 lowered to UnaExp). adding traits to ask for name and using that without=20 lowering to forward calls are the things in my TODO (along with the trait=20 to strip name info). note that i done the patch in one day, so it's neither complete nor=20 deeply tested. i simply tried to see if i can do that with minimal=20 changes to frontend. and then i found that it's fairly easy. ;-) that's why i didn't made the patch public yet -- it's not really=20 polished, and it's not really tested.=
May 31 2015
prev sibling next sibling parent ketmar <ketmar ketmar.no-ip.org> writes:
On Sun, 31 May 2015 09:43:50 -0400, Michel Fortin wrote:

 For that to be really useful the argument names should be part of the
 "A" type so you can forward them to another function and it still works.
 For instance:
=20
 	void test(string a, string b=3D"wow", string c=3D"heh") {}
=20
 	void forward(A...)(A a) {
 		test(a);
 	}
=20
 	void main() {
 		forward(c: "cc", a: "aa");
 	}
i decided to not implement that for the time being. it's too intrusive,=20 it requires alot of changes in AST and template processing, and outcome=20 of that is almost nonexistent (at least for me). actually, it requres new AST for "type with attached name", new mangling=20 for that (to stop compiler from merging instances), and new processing=20 code in various places. i see no reason to do all that work just to left=20 it lay rotting.=
Jun 04 2015
prev sibling parent ketmar <ketmar ketmar.no-ip.org> writes:
here's the patch if someone want to play with it:
http://ketmar.no-ip.org/dmd/namedargs000.patch

git am -3 should apply it to HEAD without troubles.

please note that this patch is in no way "production ready", it's more a=20
working PoC, with leftovers from earlier experiments. so code quality is=20
low-to-mediocre. ;-)

PD/WTFPL, so if someone want to take it and turn it into nice PR, he's=20
welcome.=
Jun 04 2015
prev sibling next sibling parent ketmar <ketmar ketmar.no-ip.org> writes:
ok, last sample now works too. ;-)=
May 30 2015
prev sibling parent Jacob Carlborg <doob me.com> writes:
On 2015-05-31 03:59, Michel Fortin wrote:

 I didn't know you revived that thing too. Nice.
Hehe, yeah. Not sure why I did that. I haven't even consider making it a pull request. Or what needs to be done to get it to that point.
 Make sure you take note of the related comments between Walter and me here:
 https://github.com/michelf/dmd/commit/673bae4982ff18a3d216bc1578f50d40f4d26d7a
Sure.
 At some point my plans about this changed and I wanted to implement
 named arguments differently so it'd work for template arguments too. But
 I never go to it.
I see. -- /Jacob Carlborg
May 31 2015
prev sibling next sibling parent reply Jonathan Crapuchettes <jcrapuchettes gmail.com> writes:
Two other ways to implement the concept. The first works with optional 
arguments and the other requires all arguments.

/**
 * Optional arguments and no default values; might not compile if all args
 * not passed.
 */
---------------------------------------------------------------------------
/**
 * A templated function useful for creating aliases for naming function
 * parameters.
 */
auto namedArguments(string name, T)(T arguments)
{
    static struct Args
    {
        enum string argName = name;
        T argValue;
        alias argValue this;
    }
    return Args(cast(Unqual!T)arguments);
}

///
unittest
{
    alias arg1 = namedArguments!("arg1", string);
    alias arg2 = namedArguments!("arg2", int);

    void namedArgsFunc(T...)(T arguments)
    {
        const vars = parseArguments!()(arguments);
        const string one = vars.arg1;
        const int two = vars.arg2;
    }
    namedArgsFunc(arg1(""), arg2(42));
    namedArgsFunc(arg2(56), arg1("fred"));
}

private enum isNamedArgument(T) = hasMember!(T, "argName") && hasMember!
(T, "argValue");

/**
 * Parse parameters of the type returned from $(D namedArguments) into a
 * named tuple usable in a function. 
 */
auto parseArguments(T...)(T inputs) if (allSatisfy!(isNamedArgument, T))
{
    template GetTypeAndName(ArgT)
    {
        alias Type = typeof(ArgT.argValue);
        enum string Name = ArgT.argName;
        alias GetTypeAndName = TypeTuple!(Type, Name);
    }
    auto ret = Tuple!(staticMap!(GetTypeAndName, T))();

    foreach (arg; inputs)
    {
        mixin(`ret.` ~ arg.argName) = arg.argValue;
    }

    return ret;
}

unittest
{
    alias arg1 = namedArguments!("arg1", string);
    alias arg2 = namedArguments!("arg2", int);

    const test1 = parseArguments(arg1("string"), arg2(42));
    assert(test1.arg1 == "string");
    assert(test1.arg2 == 42);

    const test2 = parseArguments(arg2(42), arg1("string"));
    assert(test2.arg1 == "string");
    assert(test2.arg2 == 42);
}
--------------------------------------------------------------------------

///All required arguments
--------------------------------------------------------------------------
/**
 * A templated function useful for creating aliases for naming function
 * parameters.
 *
 * Params:
 *     ArgTypesT = The enum type for all named parameters in a group.
 *
 * Returns: A voldemort type that can be handled by $(D parseArguments).
 */
template namedArguments(ArgTypesT) if (is(ArgTypesT == enum))
{
    auto namedArguments(ArgTypesT type, T)(T arguments)
    {
        static struct Args
        {
            alias ArgType = ArgTypesT;
            enum ArgType argsType = type;
            T args;
            alias args this;
        }
        return Args(cast(Unqual!T)arguments);
    }
}

///
unittest
{
    enum ArgTypes
    {
        Arg1,
        Arg2
    }
    alias FuncArgsT = namedArguments!ArgTypes;
    alias arg1 = FuncArgsT!(ArgTypes.Arg1, string);
    alias arg2 = FuncArgsT!(ArgTypes.Arg2, int);

    void namedArgsFunc(T...)(T arguments)
    {
        const vars = parseArguments(arguments);
        const string one = vars.Arg1;
        const int two = vars.Arg2;
    }
    namedArgsFunc(arg1(""), arg2(42));
    namedArgsFunc(arg2(56), arg1("fred"));
}

private enum isNamedArgument(T) = hasMember!(T, "argsType") && hasMember!
(T, "args");

/**
 * Parse parameters of the type returned from $(D namedArguments) into a
 * named tuple usable in a function.
 */
auto parseArguments(T...)(T inputs) if (allSatisfy!(isNamedArgument, T))
{
    template GetTypeAndName(ArgT)
    {
        import std.conv : to;
        alias Type = typeof(ArgT.args);
        enum string Name = ArgT.argsType.to!string();
        alias GetTypeAndName = TypeTuple!(Type, Name);
    }
    auto ret = Tuple!(staticMap!(GetTypeAndName, T))();

    foreach (I, arg; inputs)
    {
        ret[I] = arg.args;
    }

    return ret;
}

unittest
{
    enum ArgTypes
    {
        Arg1,
        Arg2
    }
    alias FuncArgsT = namedArguments!ArgTypes;
    alias arg1 = FuncArgsT!(ArgTypes.Arg1, string);
    alias arg2 = FuncArgsT!(ArgTypes.Arg2, int);

    const test1 = parseArguments(arg1("string"), arg2(42));
    assert(test1.Arg1 == "string");
    assert(test1.Arg2 == 42);

    const test2 = parseArguments(arg2(42), arg1("string"));
    assert(test2.Arg1 == "string");
    assert(test2.Arg2 == 42);
}
--------------------------------------------------------------------------

Jonathan
May 29 2015
parent reply "Liran Zvibel" <liran weka.io> writes:
On Friday, 29 May 2015 at 15:16:56 UTC, Jonathan Crapuchettes 
wrote:
 Two other ways to implement the concept. The first works with 
 optional
 arguments and the other requires all arguments.
 unittest
 {
     alias arg1 = namedArguments!("arg1", string);
     alias arg2 = namedArguments!("arg2", int);

     void namedArgsFunc(T...)(T arguments)
     {
         const vars = parseArguments!()(arguments);
         const string one = vars.arg1;
         const int two = vars.arg2;
     }
     namedArgsFunc(arg1(""), arg2(42));
     namedArgsFunc(arg2(56), arg1("fred"));
 }
...
 Jonathan
This is a very interesting approach, but the problem is that the person writing the function has to work to give support. I think we should try to create a wrapper function taking the original function as (alias F) that leverages ParameterIdentifierTuple , ParameterTypeTuple and ParameterDefaultValueTuple to generate the namedArguments automatically. Also, we could actually make this a Functor/Callback object that has already created members that give you easy access to the types of the named arguments. This way, we can even conceive a way to return a "partial" function with enough arguments, and only really call it when all the keyword arguments were actually provided (or enough of them were provided and the rest have provided a default value). This will allow us to create a concise API that is convenient, and strong. Liran
May 29 2015
parent reply "Liran Zvibel" <liran weka.io> writes:
On Friday, 29 May 2015 at 17:46:12 UTC, Liran Zvibel wrote:
 I think we should try to create a wrapper function taking the 
 original function as (alias F) that leverages 
 ParameterIdentifierTuple , ParameterTypeTuple and 
 ParameterDefaultValueTuple to generate the namedArguments 
 automatically.
Ketmar's work is very impressive (and I think it's better to have a nice syntax though the compiler), but since I've promised to work on it on the plane and actually did, I'm sending my implementation of a library "function" that implements Python like kw argument calling. It support positional arguments, keyword arguments and defaults. Please not that this is just a basic implementation, and will probably need some more tweaking to really support all types. Feel free to take this implementation and make sure it works in the real world :) Liran as gist: https://gist.github.com/liranz/d1a42b47f8d744db2c69 as code: below ================= import std.traits; import std.typetuple; import std.typecons; import std.string; import std.stdio; import std.conv; auto KWArg(string name, T)(T arguments) { static struct Args { T argValue; enum argName = name; alias argType = T; alias argValue this; } return Args(cast(Unqual!T)arguments); } private enum isKWArgument(T) = hasMember!(T, "argName") && hasMember!(T, "argValue"); template KWFunc(alias fun) { struct KWFunc { static int getNumOfPositionalArguments(int curr,ARGS...)() { alias types = ParameterTypeTuple!fun; static if (ARGS.length == 0) { return curr; // case that all are positional } alias first = ARGS[0]; static if (!isKWArgument!first) { static assert(is(first : types[curr]), format("positional argument of wrong type. Expected %s found %s", types[curr].stringof, ARGS[0].stringof)); return getNumOfPositionalArguments!(curr +1, ARGS[1..$])(); } else { // Finished all positional, lets make sure rest are KWArgs foreach(k, A; ARGS) { static assert(isKWArgument!A); } return curr; } } static int getPositionByArgName(int positionalArguments, string name, ARGS...)() { int ret; foreach(j, A; ARGS[positionalArguments .. $]) { static if (name == A.argName) { return j + positionalArguments; } } return -1; } static string generateCallerString(ARGS...)() { alias names = ParameterIdentifierTuple!fun; alias types = ParameterTypeTuple!fun; alias defaults = ParameterDefaultValueTuple!fun; string ret = "fun("; enum positionalArguments = getNumOfPositionalArguments!(0, ARGS)(); foreach (i, n; names) { static if (i != 0) { ret ~= ", "; } static if (i < positionalArguments) { ret ~= format("args[%s]", i); } else { enum argumentPosition = getPositionByArgName!(positionalArguments, n, ARGS); static if (-1 != argumentPosition) { alias current = ARGS[argumentPosition]; static assert(n == current.argName, format("KW Argument name ended up wrong. Expected '%s' found '%s'", n, current.argName)); static assert(is(current.argType == types[i]), format("KW argument with name %s and type '%s' conflicts ofiginal type '%s'", n, current.argType.stringof, types[i].stringof)); ret ~= format("args[%d]", argumentPosition); } else { static if (!is(defaults[i] : void)) { ret ~= to!string(defaults[i]); } else { // We were not able to place any argument. Announce failure static assert(false, format("Could not place anything for argument #%s : %s %s", i, types[i].stringof, n)); } } } } ret ~= " );"; return ret; } static auto opCall(ARGS...)(ARGS args) { enum ret = generateCallerString!ARGS(); static if(is(ReturnType!func == void)) { mixin(ret); } else { mixin("return " ~ ret); } } } } string normalFunc(string pos0, int pos1, string arg1, int arg2, long arg3 = 50, long arg4 = 100) { return format("pos0 is '%s', pos1 is '%s', arg1 is '%s' arg2 is '%s' arg3 is '%s' arg4 is '%s'", pos0, pos1, arg1, arg2, arg3, arg4); } unittest { alias arg2 = KWArg!("arg2", int); // test 2 positional argument, out of order KW arguments and default value auto ret = KWFunc!normalFunc("positional", 144, KWArg!"arg1"("full fledged"), KWArg!"arg4"(22L), arg2(2)); assert(ret == "pos0 is 'positional', pos1 is '144', arg1 is 'full fledged' arg2 is '2' arg3 is '50' arg4 is '22'"); //TODO: Add more test cases :) }
May 31 2015
parent ketmar <ketmar ketmar.no-ip.org> writes:
On Sun, 31 May 2015 11:56:47 +0000, Liran Zvibel wrote:

 Ketmar's work is very impressive (and I think it's better to have a nice
 syntax though the compiler), but since I've promised to work on it on
 the plane and actually did, I'm sending my implementation of a library
 "function" that implements Python like kw argument calling.
it doesn't hurt to have a choice. ;-) it's hard to push changes into the=20 compiler, but one can make dub library with ease. so your work is in no=20 way pointless!=
May 31 2015
prev sibling next sibling parent "Atila Neves" <atila.neves gmail.com> writes:
On Thursday, 28 May 2015 at 22:35:14 UTC, Atila Neves wrote:
 I might do a blog post on this, but here's some POC code:
Now with default values! import std.stdio; import std.range; import std.typetuple; import std.traits; import std.conv; struct Foo { int i; } struct Bar { int i; } struct Baz { int i; } void func(Foo foo = Foo(11), Bar bar = Bar(22), Baz baz = Baz(33)) { writeln("foo is ", foo); writeln("bar is ", bar); writeln("baz is ", baz); } void main() { kwargs!func(Bar(2), Baz(3), Foo(1)); //1, 2, 3 kwargs!func(Baz(3), Foo(1)); //1, 22, 3 } auto getStrArgs(alias F, T...)() { string[] strArgs; foreach(i, ParamType; ParameterTypeTuple!F) { enum index = staticIndexOf!(ParamType, T); static if(index != -1) { strArgs ~= "args[" ~ index.to!string ~ "]"; } else { alias defaultValue = ParameterDefaultValueTuple!F[i]; static if(is(defaultValue == void)) { strArgs ~= ParamType.stringof ~ ".init"; } else { strArgs ~= defaultValue.stringof; } } } return strArgs; } auto kwargs(alias F, T...)(T args) { enum strArgs = getStrArgs!(F, T); mixin("return F(" ~ strArgs.join(",") ~ ");"); }
May 29 2015
prev sibling parent reply "Atila Neves" <atila.neves gmail.com> writes:
Compile-time version for when crazy people like me pass in values 
as template parameters:


import std.stdio;
import std.array;
import std.typetuple;
import std.traits;


struct Foo { int i; }
struct Bar { int i; }
struct Baz { int i; }


void func(Foo foo = Foo(11), Bar bar = Bar(22), Baz baz = 
Baz(33))() {
     writeln("foo is ", foo);
     writeln("bar is ", bar);
     writeln("baz is ", baz);
}

void main() {
     ctKwargs!(func, Bar(2), Baz(3), Foo(1));
     writeln;
     ctKwargs!(func, Baz(3), Foo(1));
}


auto ctKwargs(alias F, T...)() {
     enum strArgs = getStrArgs!(F, T);
     mixin("return F!(" ~ strArgs.join(",") ~ ")();");
}

template typesMatch(alias T) {
     enum isRightType(alias U) = is(typeof(T) == typeof(U));
}


auto getStrArgs(alias F, T...)() {
     string[] strArgs;
     mixin("alias defaults = TemplateArgsOf!(" ~ 
F.stringof[0..$-2] ~ "!());");

     foreach(value; defaults) {
         enum ofRightType = Filter!(typesMatch!value.isRightType, 
T);
         static assert(ofRightType.length == 0 || 
ofRightType.length == 1,
                       text("Invalid type tuple ", T));
         static if(ofRightType.length == 1) {
             strArgs ~= ofRightType[0].stringof;
         } else {
             strArgs ~= value.stringof;
         }
     }

     return strArgs;
}
Jun 04 2015
parent "Atila Neves" <atila.neves gmail.com> writes:
And if you add this, it gets even more interesting:

void main() {
     alias myFunc = ctKwargify!func.wrap;
     myFunc!(Bar(2), Baz(3), Foo(1));
     myFunc!(Baz(3), Foo(1));
}

template ctKwargify(alias F) {
     template wrap(T...) {
         alias wrap  = ctKwargs!(F, T);
     }
}

template ctKwargifier(alias F) {
     template inner(T...) {
         alias ctKwargifier = ctKwargs!(F, T);
     }
}


I might have gone insane now.
Jun 04 2015