Thread overview
Flexible Default Function Parameters via structs with Nullable Fields
Apr 29, 2019
Mike Parker
Apr 30, 2019
JN
Apr 30, 2019
Adam D. Ruppe
Apr 30, 2019
Simen Kjærås
Apr 30, 2019
Adam D. Ruppe
April 29, 2019
Victor Porton shows how he uses string mixins to generate structs with Nullable fields at compile time to help him pass arbitrary subsets of explicit and default arguments to functions in D.

The blog:
https://dlang.org/blog/2019/04/29/flexible-default-function-parameters/

Reddit:
https://www.reddit.com/r/programming/comments/bip83x/flexible_default_function_parameters_in_d/
April 30, 2019
On Monday, 29 April 2019 at 13:08:59 UTC, Mike Parker wrote:
> Victor Porton shows how he uses string mixins to generate structs with Nullable fields at compile time to help him pass arbitrary subsets of explicit and default arguments to functions in D.
>
> The blog:
> https://dlang.org/blog/2019/04/29/flexible-default-function-parameters/
>
> Reddit:
> https://www.reddit.com/r/programming/comments/bip83x/flexible_default_function_parameters_in_d/

Yeah... this isn't a topic that will get any attention on Reddit. It might be nifty by D standards, but for a person not familiar with D first thought will be "why not just use named/keyword arguments", second thought will be "oh, D doesn't have them? so why not just use builder pattern then?".
April 30, 2019
On Tuesday, 30 April 2019 at 08:20:29 UTC, JN wrote:
> It might be nifty by D standards, but for a person not familiar with D

Or, as someone familiar with D, I wonder why not just use a plain struct. D allows you to set initial values for struct members plainly.

To be frank, I haven't been impressed with any of this author's posts.
April 30, 2019
On Tuesday, 30 April 2019 at 13:10:54 UTC, Adam D. Ruppe wrote:
> On Tuesday, 30 April 2019 at 08:20:29 UTC, JN wrote:
>> It might be nifty by D standards, but for a person not familiar with D
>
> Or, as someone familiar with D, I wonder why not just use a plain struct. D allows you to set initial values for struct members plainly.

Yeah, though I can see some use cases for a struct with all nullable fields and a way to combine with a regular version of that struct. This could be made a lot easier than in the article:

import std.traits : FieldNameTuple;
import std.typecons : Nullable;

struct Partial(T) if (is(T == struct)) {
    static foreach (e; FieldNameTuple!T)
        mixin("Nullable!(typeof(__traits(getMember, T, e))) "~e~";");
}

auto combine(T, PT)(T t, PT pt) if (is(PT == Partial!T)) {
    T result;
    static foreach (e; FieldNameTuple!T)
        __traits(getMember, result, e) = __traits(getMember, pt, e).get(__traits(getMember, t, e));
    return result;
}

struct S {
    int x,y;
}

unittest {
    S a = S(1,2);
    Partial!S b;
    b.x = 3;
    assert(a.combine(b) == S(3,2));
}

Now, for the abomination that is callMemberFunctionWithParamsStruct!(t, "f")(combined)... It's just t.f(combined.tupleof) in a bad disguise, and I really can't see the benefit.

Lastly, the use of a mixin to define the struct ensures you can't put methods on the struct, thus drastically reducing usability.

All in all, it's a fun beginner's project, but the quality may not be good enough that it should be on the blog.

--
  Simen
April 30, 2019
On Tuesday, 30 April 2019 at 13:44:00 UTC, Simen Kjærås wrote:
> Now, for the abomination that is callMemberFunctionWithParamsStruct!(t, "f")(combined)... It's just t.f(combined.tupleof) in a bad disguise, and I really can't see the benefit.

If you are doing function parameters, there are two kinda fun things you can do. (Personally, I kinda prefer to just do hand-written builder patters, nicer to document, often easier to read, but this is D, so let's go nuts!)


First, this is an automatically generated struct with members corresponding to function parameters:

---

void foo(int a, string cool = "low temperature", int[] c = [1, 2, 3]) {
	import std.stdio;
	writeln("a = ", a);
	writeln("cool = ", cool);
	writeln("c = ", c);
}

// this works for free functions, but not delegates, function pointers, or other callable objects
// it also will not automatically call a method, but you can build parameters for it.
struct ParamsFor(F...) if(F.length == 1) {
	static if(is(typeof(F[0]) Parameters == __parameters)) {
		static foreach(idx, _; Parameters) {
			static if(__traits(compiles, ((Parameters[idx .. idx + 1] i) => i[0])()))
			mixin("
				Parameters[idx .. idx + 1][0] // type
				"~__traits(identifier, Parameters[idx .. idx + 1])~" // name
				= ((Parameters[idx .. idx + 1] i) => i[0])() // initial value
			;");
			else
			mixin("
				Parameters[idx .. idx + 1][0] // type
				"~__traits(identifier, Parameters[idx .. idx + 1])~" // name
				// no initial value
			;");
		}
	} else static assert(0, typeof(F[0]).stringof ~ " is not a plain callable");

	auto opCall()() {
		static if(__traits(compiles, F[0](this.tupleof)))
			return F[0](this.tupleof);
		else static assert(0, __traits(identifier, F[0]) ~ " is not callable this way since it needs a `this` object, do it yourself on the outside with obj.method(params.tupleof)");
	}
}

class Test {
	void foo(int a, int b = 10, int c = 20) {
		import std.stdio;
		writeln(a, " ", b, " ", c);
	}
}

void main() {
	ParamsFor!foo params;

	params.c = [4,5,6];

	params(); // calls foo(params.tupleof) for you


	ParamsFor!(Test.foo) p2;
	p2.c = 30;
	auto f = new Test();

	//p2(); // will static assert cuz of this

	f.foo(p2.tupleof); // use this instead
}

---



But there, required parameters can be left out too - you don't have to set anything. (It also doesn't work with const params and other such troubles, but that really complicates this idea - and is part of why I prefer a hand-written builder thing, so you can handle all those details explicitly.)


We can solve that with a constructor. Right below the first static if in the example, add:

---
		static if(!__traits(compiles, ((Parameters _) {}) () )) {
			@disable this();
			this(Parameters params) {
				this.tupleof = params;
			}
		}
---

And now you get an obscure error if you don't specific parameters when creating the Params object. But.... eh I don't love it.



Regardless, still though, this stuff is kinda cool. And if you combine with the `with` statement:


---
void main() {
        // this assumes the version with the constructor
        // but if you didn't add that code, just remove
        // the 5 and thus ParamsFor!foo()
        with(ParamsFor!foo(5)) {
                c = [4,5,6]; // set the param c...
                opCall(); // call the function
        }
}
---

so yeah, kinda cool.