Keyword Arguments vs Dict

I would like to know if it is a problem to use a Dict parameter for passing options to a method instead of using the keyword arguments mechanism already present in Julia.

Basically, I have a method that takes a ten or more of keyword parameters, and I will allow users to call this method by a script with flags that correspond to that parameters. Seems to me that the best to simplify the things is the method take the ArgParse parsed flags Dict so the names are consistent between internal and external calling, and I do not have to create a boilerplate method that just takes the dict and calls the method feeding the dict values to the keyword arguments. If it was not by the fact the flag names have hyphens in the middle of them, I suppose I could just splat the Dict in the method call with keyword arguments.

Not sure if I understand the question, but you can take ArgParse dictionary and convert to named tuple, then call your method with keyword args by spreading this tuple.

Example:

function asdf(x = 1, y = 4) return x, y; end
nt = (x=4, y=134)
asdf(nt...)

I was asking more in the sense of “am I throwing away something I did not perceive? I mean, the argument keywords would not have been developed if it had basically no advantage over using a Dict, no? yes? maybe?”. However, your link suggestion brings a good hybrid solution to my eyes: I can have flags and parameter names that just differ between each other because one uses dashes and the other uses underlines, and have a simple conversion method that I will not need to update each time I add a new flag or change a flag name. It will also be easy to the user to do the mental conversion.

I’m not able to judge your preferences without more context. My personal opinion is that dictionaries should be avoided due to their lack of performance and elegance. Value tuples and named keyword arguments are better for me, for instance they allow autocompletion in code editor.
If your model is really complicated and have some business rules you need to validate I would define some struct, maybe some hierarchy of structs (you can go abstract types or nested structs, or both). Probably your params have some defaults in case of missing user input. We had quite interesting discussion in thread below about default values for fields. There are also other threads about this topic.
We can always define some macros if you have an idea for better syntax for handling that, user input in scripts is a common problem I am also struggling.

Our circumstances are clearly different.

My personal opinion is that dictionaries should be avoided due to their lack of performance and elegance.

I care for neither of these problems. The methods that take the Dictionaries can take from seconds to hours to execute, so any overhead is irrelevant. The definition of elegance is vague and depends on context, I am not sure if using a Dict is less elegant in my case.

for instance they allow autocompletion in code editor.

I do not use such autocompletion.

maybe some hierarchy of structs

Far too much semantic overhead for no gain, in my opinion.

We can always define some macros if you have an idea for better syntax for handling that, user input in scripts is a common problem I am also struggling.

Really, an effort that is only justified if it could help more people. What I did in the end was to modularize the definition of ArgParse tables and methods to check on them. This way, for each important method that I want command-line arguments to reach, I define an ArgParse table and a method to check for errors in such arguments. The script itself just collect all ArgParse tables, call parse_args, and then check all arguments for errors using the methods for error checking. Then, in the method itself, for the case it was not called by the means of the script, also uses the same ArgParse table and error-checking method to check itself. This way there is no duplication, and there cannot be an inconsistency between the parameters defined by the method and the ones gathered by the script.

If you have many keyword-ish args, a dictionary is perfectly reasonable. If there are few, you certainly still may – it is not considered best practice in that circumstance, in part because it may make for less transparent source code.

1 Like

I usually prefer a dedicated type for this, but either choice could be fine, depending on the details.

A particular caveat with Dict may be the lack of a concrete type (for heterogeneously typed values), which could lead to suboptimal code. Keywords are implicitly NamedTuples, so this problem does not arise. You should check with @code_warntype and see if this is a problem for your application.

2 Likes

I have a counter example. As you can see in this commit I leveraged type system and replaced 20 lines of if/else dispatch to simple 5-line structure and defined functors on it. Using Julia’s type system is a pure gain.

True. If, in my case, performance was relevant (i.e., it was an argument for a small method of an API that is often called inside loops), this would be the best course of action. But, in this case, I literally call the script to execute 1~2 to 20~100 times the same method, and each time has a time limit of one hour (I let the run in a remote server and forget them for some time). Having an easy to write/extend and safe code is much more relevant than a performance-oriented or idiomatic code here.

Hmmm, it is not my intent (for now) to be contrarian, but I have to point that: (1) considering both files, the total number of lines/words increased (if this is your criteria) and the complexity (in terms of more advanced concepts used) also increased; (2) the parsing library you are using is interesting, but for my case I prefer ArgParse.jl; DocOpt seems to be impracticable in my case (I have 3+ positional arguments and 20+ options, some that take extra arguments if they are present, and other that just set booleans if they are present). It, however, seems like a good choice for you.

I already have the arguments information stored in the ArgParse structure that I will use anyway because of the sheer complexity of trying to parse this by hand. So instead of replicating it in a type hierarchy I prefer to keep a single source of truth using the ArgParse.

Finally, I need to point that you have many distinct methods that you call depending on which exact combination of the same parameters was passed. In such case, the “pattern matching” made by the multiple dispatch is an elegant replacement of a switch case (or a chain of if-elses), you basically outsourced this structure to the generic code of the dispatcher. In my case I have one single required parameter that receive the name of the method called (selected inside the code by reflexivity, to keep the SSoT) and many different parameters for each method (as well other that are generic for all methods) and I need to be ready to parse all of them, and then after check if parameters of an non-selected method were passed.

You are right the code I showed is over-engineered, but I was just experimenting. Normally I would skip this structure and defined a function with methods that have combination of numbers and nothings as parameters. It would work the same with much lower LoC.

From my point of view this pattern is not SSoT but rather “tightly coupling”. If something will be changed by ArgParse developers the update might require changes in the application logic, and you cannot easily replace ArgParse with DocOpt or anything else. You also cannot test your core logic without thinking about how ArgParse works. SSoT would be applied if you generated ArgParse configuration from the data model. Good thing is that ArgParse returns dictionary, not a custom type, so no need to import ArgParse - not so tightly coupled as it could be in an extremal case.
I am happy that it works for you well and I am just expressing my point of view here. Sorry if I sounded offensive, it was not my intent.

1 Like

Sorry if I sounded offensive, it was not my intent.

In no way you sounded offensive to me, do not worry. I am now worried that someway I gave you the impression that I was offended, XD.

From my point of view this pattern is not SSoT but rather “tightly coupling”.

You are kinda right about this. What I should do is to have a data model that is very similar to ArgParse and use it to generate both the ArgParse configuration as the other generic helper methods that I need. But I am lazy, XD. The fact is the ArgParse configuration is already basically everything I need (so it is easier to use it instead of creating a custom type) and if ArgParse changes and break my code, well, it will already break a good deal of code, decoupling will reduce this a little but if this happens I will already have to patch as fast as possible. But I am now considering to do this.