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.