Best practice of passing a large number of parameters to functions

34

23

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.

U.T.

Posted 2014-07-25T10:23:28.763

Reputation: 533

I notice that you never Accepted an answer to this question. Does anything remain unaddressed or unsatisfactory? – Mr.Wizard – 2014-12-08T21:56:27.393

Answers

27

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

Global`g

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.

Mr.Wizard

Posted 2014-07-25T10:23:28.763

Reputation: 259 163

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. – U.T. – 2014-07-25T12:22:12.010

@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 – 2014-07-25T12:45:48.553

@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 – 2014-07-25T12:50:21.080

defWithOpts looks great. Thanks a lot! – U.T. – 2014-07-25T13:00:35.593

@UweThümmel You're welcome. Let me know if you have any trouble with it. – Mr.Wizard – 2014-07-25T13:02:37.220

@UweThümmel Yet another extension added. – Mr.Wizard – 2014-07-25T13:59:24.507

Ahh, I see what brought up your inquiry into my question about With. Nice, +1. – bobthechemist – 2014-07-25T17:54:22.337

20

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

enter image description here

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.

Murta

Posted 2014-07-25T10:23:28.763

Reputation: 23 859

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 – 2014-07-25T11:19:35.797

@Mr.Wizard tks for your comment. I changed f to accept default value for par. – Murta – 2014-07-25T11:33:10.393

1Be 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 – 2014-07-25T11:36:24.610

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 – 2014-07-25T11:39:35.123

4For completeness, the one-liner Plot[PDF[LogNormalDistribution[#mu, #sigma], x], {x, #lb, #ub}] &[par] works. – Daniel W – 2014-07-25T16:35:28.163

@Mr.Wizard Can you explain to me why p:_Association:par does not work in this case? Is it because par does not match _Association in its unevaluated form? – sebhofer – 2014-07-27T09:21:58.053

@sebhofer If par is already defined that pattern will work but the current values will be hard-coded. If par is undefined or Blocked it will fail because as you note par does not match _Association, and the default must match the pattern. – Mr.Wizard – 2014-07-27T09:33:42.643

@Mr.Wizard I was referring to the latter case. Thx for the explanation! – sebhofer – 2014-07-27T09:35:01.763

After having give a quick try to Association and a more thorough one to OptionsPattern I have to say that I really prefer OptionsPattern. In deed OptionsPattern can have defaults, which in some cases is very handy. Also one can have a quick summary of the inputs taken by a function by just doing the standard Options[functions]. If OptionValue is too long to type, which I can agree upon, a quick OV[x_]:=OptionValue[x] would do the job. Any impressions against or in favor of my comparison of the two possibilities? – Rho Phi – 2015-05-13T14:39:01.517