# Best practice of passing a large number of parameters to functions

I have a number of functions that all take a large number of parameters. I am wondering what is the best practice of passing these parameters to those functions. I could, of course, simply specify the parameters outside the functions, as in (note that, in the actual example, there are far more parameters)

mu=1;
sigma=1;
lb=0;
ub=10;
f[x_] := PDF[LogNormalDistribution[mu, sigma],x]


However, I would prefer to explicitly pass the parameters to the functions. In Python, I would use a dictionary. In Mathematica, one possibility would be a replacement rule.

par={mu->1,sigma->1,lb->0,ub->10};
f[x_,par_] := PDF[LogNormalDistribution[mu, sigma],x]/.par


However, this can cause warnings if a function only takes numerical arguments, e.g.

Plot[f[x,par],{x,lb,ub}]/.par


Plot::plln: Limiting value lb in {x,lb,ub} is not a machine-sized real number. >>

The plotting actually, works, though.

Also, passing parameters using replacement rules seems to be inefficient, since - if possible - evaluations are done symbolically, and only then are values substituted for variables.

-
I notice that you never Accepted an answer to this question. Does anything remain unaddressed or unsatisfactory? –  Mr.Wizard Dec 8 '14 at 21:56

# Basic proposal

There are a number of options and their attractiveness will depend on the scenario for their use, therefore it is difficult to make any broad recommendations of best practice.

I will say that generally it is not recommended to rely on global assignments as in your first example, because this method scales poorly and because it is easy to make mistakes and get invalid results.

One approach you might consider is this:

Options[defs] = {mu -> 1, sigma -> 1, lb -> 0, ub -> 10};

f[x_, OptionsPattern[defs]] :=
PDF[LogNormalDistribution[OptionValue[mu], OptionValue[sigma]], x]


Now you can call f with one argument:

f[1.6]

0.216668


Or you can override values with explicit Options:

f[1.6, mu -> 1.7]

0.117023


You can also quickly change a value using SetOptions:

SetOptions[defs, sigma -> 2]

{mu -> 1, sigma -> 2, lb -> 0, ub -> 10}

f[1.6]

0.120368


Note: making assignments to the Option names (e.g. mu = 1) will break your code.
Consider either Protect-ing these Symbols or using Strings instead, e.g. "mu" -> 1.

One disadvantage of this method is that it lengthens definitions. Sometimes using With makes these more clear:

f[x_, OptionsPattern[defs]] :=
With[{mu = OptionValue[mu], sigma = OptionValue[sigma]},
PDF[LogNormalDistribution[mu, sigma], x]
]


This can be streamlined using listWith from: Constructing symbol definitions for With:

SetAttributes[listWith, HoldAll];

listWith[(set : Set | SetDelayed)[L_, R_], body_] :=
set @@@ Thread[Hold @@@ {L, R}, Hold] /. _[x__] :> With[{x}, body]


Now:

f[x_, OptionsPattern[defs]] :=
listWith[{mu, sigma} = OptionValue[{mu, sigma}],
PDF[LogNormalDistribution[mu, sigma], x]
]


# Automation

### Manual specification

Although this will not work with String parameter names here is a method to further automate function construction:

SetAttributes[defWithOpts, HoldAll]

defWithOpts[
sym_Symbol,
opts : {__Symbol},
(set : Set | SetDelayed)[h_[args___], RHS_]
] :=
set[
h[args, OptionsPattern[sym]],
listWith[opts = OptionValue[opts], RHS]
]


Now:

defWithOpts[def, {mu, sigma},
g[x_] := PDF[LogNormalDistribution[mu, sigma], x]
]


And the definition that was created:

?g


Globalg

g[x_, OptionsPattern[def]] :=
listWith[{mu, sigma} = OptionValue[{mu, sigma}],
PDF[LogNormalDistribution[mu, sigma], x]]


For code specific to String parameter names see: How to write complex function definitions at run time?

### Automatic detection

Yet another idea for automation, built on the assumption that you will first define the Options list (with dummy values if necessary) then the functions. It works by finding all cases of parameter (Option) names within the right-hand-side of the definition.

SetAttributes[defAutoOpts, HoldAll]

defAutoOpts[
sym_Symbol: defs,
(set : Set | SetDelayed)[h_[args___], RHS_]
] :=
Cases[Unevaluated@RHS, Alternatives @@ Options[sym][[All, 1]], {-1}, Heads -> True] //
set[h[args, OptionsPattern[sym]], listWith[# = OptionValue[#], RHS]] &


Now you can do this:

defAutoOpts[
h[x_] := PDF[LogNormalDistribution[mu, sigma], x]
]


Which creates:

h[x_, OptionsPattern[defs]] :=
listWith[{mu, sigma} = OptionValue[{mu, sigma}],
PDF[LogNormalDistribution[mu, sigma], x]]


You can also call defAutoOpts[defs2, . . .] to use a different parameter list.

-
Great! Would it be possible to construct listWith such that it only needs {mu,sigma} and body as an argument? I have tried kwargs[list_, f_] := listWith[list = OptionValue[list], f] but that does not work. –  Uwe Thümmel Jul 25 '14 at 12:22
@UweThümmel I'm glad this is helpful. I actually briefly had code in my answer that is somewhat like what you request but I removed it as it would not work with String parameter names. I shall assume that doesn't concern you and add it back. You probably needed HoldAll for kwargs but depending on how you are using it you will also need either an OptionsPattern[] or OptionValue[defs, list]. –  Mr.Wizard Jul 25 '14 at 12:45
@UweThümmel Please look at defWithOpts and tell me if this does what you would like or if you wish to explore further development of kwargs. –  Mr.Wizard Jul 25 '14 at 12:50
defWithOpts looks great. Thanks a lot! –  Uwe Thümmel Jul 25 '14 at 13:00
@UweThümmel You're welcome. Let me know if you have any trouble with it. –  Mr.Wizard Jul 25 '14 at 13:02

In V10, another option is to use Association.

par=<|"mu"->1,"sigma"->1,"lb"->0,"ub"->10|>;

f[x_, p_Association:par] := PDF[LogNormalDistribution[p["mu"], p["sigma"]], x]

Plot[f[x, ##], {x, #lb, #ub}] &@par


Another form for Plot is:

Plot[f[x, par], {x, par@"lb", par@"ub"}]


And as @Mr.Wizard commented, you can use the default value for par, omitting it:

Plot[f[x], {x, par@"lb", par@"ub"}]


I like Associations because notation is much simpler than Options rule. The disadvantage is that they don't have filters as Options, and Associations do not accept pattern tests.

-
I see that I overlooked the use of lb and ub in Plot. I do like the look of this approach, though I have not yet used it myself. The way you wrote this one will need to pass par to f every time it is called; you could improve that by making par the default value for p: p_:Unevaluated[par]. I think you also lose the ability to override values by supplying an Option (at least cleanly). –  Mr.Wizard Jul 25 '14 at 11:19
@Mr.Wizard tks for your comment. I changed f to accept default value for par. –  Murta Jul 25 '14 at 11:33
Be aware that you just hard-coded the current value of par as the default. If the user later changes the dictionary this will lead to error and confusion. –  Mr.Wizard Jul 25 '14 at 11:36
Oops: my recommendation p_ : Unevaluated[par] doesn't work either. Sadly Default will have problems of its own. I'm afraid you'll need something like this on every definition: Block[{par}, f[x_, p : (_Association | par) : par] := . . . ] –  Mr.Wizard Jul 25 '14 at 11:39
For completeness, the one-liner Plot[PDF[LogNormalDistribution[#mu, #sigma], x], {x, #lb, #ub}] &[par]` works. –  Daniel W Jul 25 '14 at 16:35