www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Why I love D: function parameter reification

reply "H. S. Teoh" <hsteoh qfbox.info> writes:
Given the volume of negative posts about D around these parts, I thought
I'd offer some counterpoint: why I love D.  I'm planning to post this
every now and then, as a sort of informal series.

Today, I grappled with the following situation: I have a bunch of
functions with parameters that perform certain operations, that I want
to expose to the user via the program's command-line arguments.  Also,
for maximum user-friendliness, the program's help text should accurately
describe each function (treated as a subcommand on the command line) and
what parameters each takes.

The traditional solution, of course, is simple: write a showHelp()
function that prints some text describing each function and its
parameters, then in main() write a big switch statement over function
name, and each case block parses the program arguments, converts them to
a suitable form, and invokes the function.

It's not a bad solution, but involves a lot of tedious work that,
because of their repetitious nature, is liable to human error.  Every
time I added a new function, I have to change two other places in the
code (update the help text, add a new case block). Raise your hand,
whoever has encountered out-of-sync/outdated help texts in programs. :-P

So I sought for a better solution, in the spirit of DRY: put said
functions into a wrapper struct, mark them static, and add a string UDA
that will be introspected by showHelp() to generate a list of functions
that's guaranteed to be up-to-date, and by main() to generate the
requisite case blocks.

The help text part is simple: just introspect the wrapper struct,
extract the function name, the string UDA for the description, and the
list of parameter names, print them out. Easy.

The case blocks are a little trickier.  It would have been simple if
every function had the same set of parameters: then it's just a matter
of mixing in the function name in a standard call.  But what if each
function had a *different* set of parameters?

My initial attempt was to iterate over the parameters and generate code
for parsing each one, assign them to temporary variables, then create a
list containing the list of temporary variables to use as a mixin to
pass them all to the target function. Would work, but would involve a
lot of hairy, ugly code.

And then inspiration struck: why do I need to individually declare those
variables (and invent names for each one)? I don't need to. I can get a
hold of the function's parameters as a parameter tuple using
`is(typeof(func) Params : __parameters)`, then declare a compound
variable with this parameter tuple as its type:

	void main(string[] args) {
		...
		static if (is(typeof(myfunc) Params : __parameters)) {
			Params funcArgs; // use a single name for ALL function arguments

			// Now populate the arguments by converting command-line
			// arguments to the right types:
			foreach (i, T; Params) {
				// std.conv.to is Da Bomb
				import std.conv : to;
				funcArgs[i] = args[i+2].to!T;
			}

			// To call the function, we just pass the entire
			// aggregate to it in one shot:
			myfunc(funcArgs); // thanks to the magic tuple
					// type, this auto-expands
					// funcArgs into multiple
					// arguments
		}
	}

The compound variable 'funcArgs' is a reification of the target
function's arguments; this allows us to manipulate it like a
pseudo-array in the loop that parses command-line arguments. We don't
need to construct any mixins involving cumbersome parameter lists! (Of
course, in the actual code a mixin is still needed in order to bind
`myfunc` to the actual target function, which is iterated over by name.)

I couldn't even begin to imagine how to pull off something like this in
C++... for sure, it will be NOWHERE near as elegant as the above.

D r0x0rs!!!


T

-- 
War doesn't prove who's right, just who's left. -- BSD Games' Fortune
Jun 08 2022
next sibling parent reply forkit <forkit gmail.com> writes:
On Wednesday, 8 June 2022 at 21:47:16 UTC, H. S. Teoh wrote:

as I recall, it was *your* post about "Using closure in function 
scope to make "real" private class members" that started this 
whole thing.

;-)

Sadly, this idea is *always* responded to, by others, in a 
typical passive/aggressive manner (which is what causes all the 
controversy):

https://medium.com/the-mission/5-tactics-used-by-passive-aggressive-arguers-and-the-best-forms-of-defense-42a9348b60ed
Jun 08 2022
parent reply "H. S. Teoh" <hsteoh qfbox.info> writes:
On Wed, Jun 08, 2022 at 10:02:01PM +0000, forkit via Digitalmars-d wrote:
 On Wednesday, 8 June 2022 at 21:47:16 UTC, H. S. Teoh wrote:
 
as I recall, it was *your* post about "Using closure in function scope to make "real" private class members" that started this whole thing. ;-) Sadly, this idea is *always* responded to, by others, in a typical passive/aggressive manner (which is what causes all the controversy): https://medium.com/the-mission/5-tactics-used-by-passive-aggressive-arguers-and-the-best-forms-of-defense-42a9348b60ed
LOL... I've been around here since 2011, it's obvious that certain personages are around for the sole purpose of stirring up the mud. Others are good faith complainants who have been struggling with long-term unresolved issues (sometimes I'm among them). Put these two together, and this is what you get. :-/ If one tries hard enough, one can always find *something* to complain about. For example, in the code I posted in this thread, there's that is(... : __parameters) construct which has some, shall we say, quirky, behaviours, about which I wrote years ago: https://forum.dlang.org/thread/vpjpqfiqxkmeavtxhyla forum.dlang.org If you really wanted to, that thread could be the basis of another interminable complaint thread about is(...). Instead, I found something to like about it, which is the whole point of *this* thread. What can I say? It's one of those glass-half-full vs. glass-half-empty scenarios. It speaks more about the person(s) than about the D language itself. :-D T -- Life would be easier if I had the source code. -- YHL
Jun 08 2022
parent forkit <forkit gmail.com> writes:
On Wednesday, 8 June 2022 at 23:06:52 UTC, H. S. Teoh wrote:
 On Wed, Jun 08, 2022 at 10:02:01PM +0000, forkit via 
 Digitalmars-d wrote:
 On Wednesday, 8 June 2022 at 21:47:16 UTC, H. S. Teoh wrote:
 
as I recall, it was *your* post about "Using closure in function scope to make "real" private class members" that started this whole thing. ;-) Sadly, this idea is *always* responded to, by others, in a typical passive/aggressive manner (which is what causes all the controversy): https://medium.com/the-mission/5-tactics-used-by-passive-aggressive-arguers-and-the-best-forms-of-defense-42a9348b60ed
LOL... I've been around here since 2011, it's obvious that certain personages are around for the sole purpose of stirring up the mud. Others are good faith complainants who have been struggling with long-term unresolved issues (sometimes I'm among them). Put these two together, and this is what you get. :-/ If one tries hard enough, one can always find *something* to complain about. For example, in the code I posted in this thread, there's that is(... : __parameters) construct which has some, shall we say, quirky, behaviours, about which I wrote years ago: https://forum.dlang.org/thread/vpjpqfiqxkmeavtxhyla forum.dlang.org If you really wanted to, that thread could be the basis of another interminable complaint thread about is(...). Instead, I found something to like about it, which is the whole point of *this* thread. What can I say? It's one of those glass-half-full vs. glass-half-empty scenarios. It speaks more about the person(s) than about the D language itself. :-D T
Well, look, in the end, the argument for co-operative mutability for code within a module, is valid - in some cases. In some cases, it's not valid. Both points of view are valid. So, yes, it's a glass half full...thing. There is something to 'like' about how the D module implements private, and there is also something to 'not like' about it. That only one side can be correct in this argument, is (as Spock would say).. illogical. D is missing the feature I find to be one of the most important features in my design considerations. That's worth speaking up about. So I'd encourage everyone to 'speak up' about the features that are important to them. Life is change. No change. No life.
Jun 08 2022
prev sibling next sibling parent reply Adam D Ruppe <destructionator gmail.com> writes:
On Wednesday, 8 June 2022 at 21:47:16 UTC, H. S. Teoh wrote:
 (Of course, in the actual code a mixin is still needed in order 
 to bind `myfunc` to the actual target function, which is 
 iterated over by name.)
You probably don't need that either. But yeah getting udas off function parameters is a bit... fun, but it sure does allow a lot of cool things.
Jun 08 2022
parent reply Paolo Invernizzi <paolo.invernizzi gmail.com> writes:
On Wednesday, 8 June 2022 at 23:09:49 UTC, Adam D Ruppe wrote:
 On Wednesday, 8 June 2022 at 21:47:16 UTC, H. S. Teoh wrote:
 (Of course, in the actual code a mixin is still needed in 
 order to bind `myfunc` to the actual target function, which is 
 iterated over by name.)
You probably don't need that either. But yeah getting udas off function parameters is a bit... fun, but it sure does allow a lot of cool things.
I still think that being able to reflect ddoc comments will be a huge win ... a-la-python-"__doc__"
Jun 09 2022
parent reply Adam D Ruppe <destructionator gmail.com> writes:
On Thursday, 9 June 2022 at 08:19:05 UTC, Paolo Invernizzi wrote:
 I still think that being able to reflect ddoc comments will be 
 a huge win ... a-la-python-"__doc__"
Yes, indeed, I've requested this several times, it is useful for a bunch of things like this. There were PRs adding `__traits(docComment, symbol)` to get it too, but rejected by people who don't use D in the real world. Alas.
Jun 09 2022
next sibling parent reply bauss <jj_1337 live.dk> writes:
On Thursday, 9 June 2022 at 11:39:35 UTC, Adam D Ruppe wrote:
 On Thursday, 9 June 2022 at 08:19:05 UTC, Paolo Invernizzi 
 wrote:
 I still think that being able to reflect ddoc comments will be 
 a huge win ... a-la-python-"__doc__"
Yes, indeed, I've requested this several times, it is useful for a bunch of things like this. There were PRs adding `__traits(docComment, symbol)` to get it too, but rejected by people who don't use D in the real world. Alas.
I really can't see any reason to reject it, it's not like it'll impact anything at all really. But it really would be useful for tooling, error messages, validation etc. Could in theory be used to create automated unittests by extracting code examples from the docs.
Jun 09 2022
next sibling parent reply Adam D Ruppe <destructionator gmail.com> writes:
On Thursday, 9 June 2022 at 11:43:36 UTC, bauss wrote:
 I really can't see any reason to reject it, it's not like it'll 
 impact anything at all really.
The stated reason was an ideology that someone might misuse it to sneak semantic info into comments. The compiler implementation also discards them unless you use the -D switch, but that'd be fairly easy to fix (or even letting it stay blank unless you passed the switch would probably be acceptable too) so implementation shouldn't be a big deal, but then we're told that'd increase memory usage and compile times. Cuz I'm sure some text comments are a big concern.
 But it really would be useful for tooling, error messages, 
 validation etc.

 Could in theory be used to create automated unittests by 
 extracting code examples from the docs.
Yeah, or even just pulling in for for wrapped script languages, auto-generated command line help or web site forms. Lots of nice little things.
Jun 09 2022
parent bauss <jj_1337 live.dk> writes:
On Thursday, 9 June 2022 at 11:49:32 UTC, Adam D Ruppe wrote:
 The stated reason was an ideology that someone might misuse it 
 to sneak semantic info into comments.
I mean you can abuse and misuse a lot of things, you just trust users not to do it and when they do it then let them deal with it.
Jun 09 2022
prev sibling parent "H. S. Teoh" <hsteoh qfbox.info> writes:
On Thu, Jun 09, 2022 at 11:43:36AM +0000, bauss via Digitalmars-d wrote:
 On Thursday, 9 June 2022 at 11:39:35 UTC, Adam D Ruppe wrote:
 On Thursday, 9 June 2022 at 08:19:05 UTC, Paolo Invernizzi wrote:
 I still think that being able to reflect ddoc comments will be a
 huge win ... a-la-python-"__doc__"
Yes, indeed, I've requested this several times, it is useful for a bunch of things like this. There were PRs adding `__traits(docComment, symbol)` to get it too, but rejected by people who don't use D in the real world. Alas.
I really can't see any reason to reject it, it's not like it'll impact anything at all really.
In theory, you could insert code in your ddoc and use introspection to mix it into the program and execute it at runtime.
 But it really would be useful for tooling, error messages, validation
 etc.
 
 Could in theory be used to create automated unittests by extracting
 code examples from the docs.
Yes, this would be an awesome application. Though, for the most part, it has already been covered by ddoc'd unittest blocks. T -- Freedom: (n.) Man's self-given right to be enslaved by his own depravity.
Jun 09 2022
prev sibling parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 6/9/22 7:39 AM, Adam D Ruppe wrote:
 On Thursday, 9 June 2022 at 08:19:05 UTC, Paolo Invernizzi wrote:
 I still think that being able to reflect ddoc comments will be a huge 
 win ... a-la-python-"__doc__"
Yes, indeed, I've requested this several times, it is useful for a bunch of things like this. There were PRs adding `__traits(docComment, symbol)` to get it too, but rejected by people who don't use D in the real world. Alas.
An update to ddoc to also parse e.g. ` ddoc(string)` as ddoc comments would be fine. Allowing actual comments to affect the compilation of code is not fine. -Steve
Jun 09 2022
parent Adam Ruppe <destructionator gmail.com> writes:
On Thursday, 9 June 2022 at 16:39:44 UTC, Steven Schveighoffer 
wrote:
 An update to ddoc to also parse e.g. ` ddoc(string)` as ddoc 
 comments would be fine.
Yeah, I did that for some special purposes already (it is nice to have your own doc generator). But doc(q"( )") is quite a bit wordier than /++ +/ lol
 Allowing actual comments to affect the compilation of code is 
 not fine.
Oh please.
Jun 09 2022
prev sibling parent Andrey Zherikov <andrey.zherikov gmail.com> writes:
On Wednesday, 8 June 2022 at 21:47:16 UTC, H. S. Teoh wrote:
 Today, I grappled with the following situation: I have a bunch 
 of functions with parameters that perform certain operations, 
 that I want to expose to the user via the program's 
 command-line arguments.  Also, for maximum user-friendliness, 
 the program's help text should accurately describe each 
 function (treated as a subcommand on the command line) and what 
 parameters each takes.

 The traditional solution, of course, is simple: write a 
 showHelp() function that prints some text describing each 
 function and its parameters, then in main() write a big switch 
 statement over function name, and each case block parses the 
 program arguments, converts them to a suitable form, and 
 invokes the function.
If you change your functions to callable structs moving function arguments to struct members then you can get command line parsing, help text generation and much more with just few lines of code using [argparse](https://code.dlang.org/packages/argparse)
Jun 08 2022