Jump to page: 1 2 3
Thread overview
As discussed in DConf2015: Python-like keyword arguments
May 28, 2015
Atila Neves
May 29, 2015
Jacob Carlborg
May 29, 2015
Michael Coulombe
May 30, 2015
ketmar
May 30, 2015
ketmar
May 31, 2015
Michel Fortin
May 31, 2015
ketmar
May 31, 2015
Idan Arye
May 31, 2015
Michel Fortin
May 31, 2015
ketmar
Jun 05, 2015
ketmar
Jun 05, 2015
ketmar
May 31, 2015
ketmar
May 31, 2015
Jacob Carlborg
May 29, 2015
Liran Zvibel
May 31, 2015
Liran Zvibel
May 31, 2015
ketmar
May 29, 2015
Atila Neves
Jun 04, 2015
Atila Neves
Jun 04, 2015
Atila Neves
May 28, 2015
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 29, 2015
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
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
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
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
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 30, 2015
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 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 30, 2015
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="wow", string c="heh") {
    assert(b == "wow");
  }
  test(c: "cc", a: "aa");

the long-wanting feature of "use defaults for some args". i like it.

May 31, 2015
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 31, 2015
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:
> 
>> 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.

my work now allows this:
  string test (string a, string b="wow", string c="heh") {
    return a~b~c;
  }

  void main () {
    enum str = test(c: "cc", a: "aa");
    assert(str == "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="wow", string c="heh") {
    import std.conv : to;
    return to!string(a)~b~c;
  }

  void main () {
    enum str = test(c: "cc", a: 42); // can't
    //enum str = test(a: 42, c: "cc"); // WORKS
    assert(str == "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 mtype.c and processing of NamedArgExp at different visitors). .di generation is supported too.

« First   ‹ Prev
1 2 3