As discussed in DConf2015: Python-like keyword arguments

Liran Zvibel via Digitalmars-d digitalmars-d at puremagic.com
Sun May 31 04:56:47 PDT 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 :)
}




More information about the Digitalmars-d mailing list