May 31, 2015
ok, last sample now works too. ;-)

May 31, 2015
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
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
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
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
On Sun, 31 May 2015 09:43:50 -0400, Michel Fortin wrote:

> 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!

as for named calls argument order doesn't really matter (i.e. it tries to 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 do: just try the original order first (as missing arg means "reordering" 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);
>>   }
>> 
>>   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.

yes, i'm eventually planning to do that. inside the frontend they are NamedArgExp instances, so the information is here (althru they eventually lowered to UnaExp). adding traits to ask for name and using that without lowering to forward calls are the things in my TODO (along with the trait to strip name info).

note that i done the patch in one day, so it's neither complete nor deeply tested. i simply tried to see if i can do that with minimal 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 polished, and it's not really tested.

May 31, 2015
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 compiler, but one can make dub library with ease. so your work is in no way pointless!

June 04, 2015
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;
}
June 04, 2015
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.
June 05, 2015
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:
> 
> 	void test(string a, string b="wow", string c="heh") {}
> 
> 	void forward(A...)(A a) {
> 		test(a);
> 	}
> 
> 	void main() {
> 		forward(c: "cc", a: "aa");
> 	}

i decided to not implement that for the time being. it's too intrusive, it requires alot of changes in AST and template processing, and outcome of that is almost nonexistent (at least for me).

actually, it requres new AST for "type with attached name", new mangling for that (to stop compiler from merging instances), and new processing code in various places. i see no reason to do all that work just to left it lay rotting.