Common pattern for long list of configuration arguments

I have multiple functions with a long list of configuration arguments.
Calling those functions makes a lot clutter visually:

filteredValue = function1(someValue, cfg1, cfg2, cfg3)
finalValue = function2(filteredValue, cfg1, cfg2, cfg3, cfg4)

I wonder if there is common pattern in functional programming which overcomes this problem. Two possibilities come into my mind:

  • Put all configuration variables in a configuration type with several subtypes and only pass this config type to the function:
immutable Filter1
    cfg1::Float64
    cfg2::Float64
end
immutable Filter2
    cfg2::Float64
end
immutable Cfg
    filter1::Filter1
    filter2::Filter2
end
...
filteredValue = function1(someValue, cfg)
finalValue = function2(filteredValue, cfg)

However in this case the function has to know the structure of cfg, which is not at all ideal.
The other method that comes into my mind is:

  • Put all configuration values in a type as shown above, but instead of passing that to the function itself, create an extra function, which initilizes the function in need using closures:
function initFilter1(cfg)
    return X -> function1(X, cfg.filter1.cfg1, cfg.filter1.cfg2, cfg.filter2.cfg3)
end
function initFilter2(cfg)
    return X -> function2(X, cfg.filter1.cfg1, cfg.filter1.cfg2, cfg.filter1.cfg3, cfg.filter2.cfg4)
end
...
filter1 = initFilter1(cfg)
filter2 = initFilter2(cfg)

filteredValue = filter1(someValue)
finalValue = filter2(filteredValue)

The cost of this method is the additional function that is needed for every function with a long list of arguments. Is this a common pattern in functional programming?
Are there other patterns?

However in this case the function has to know the structure of cfg, which is not at all ideal.

Why not? It seems like you just need a constructor for your Config type which provides defaults.

type Config
    c1::Float64
    c2::Float64
    c3::Float64
end

Config(;c1=1.0, c2=2.0, c3=3.0) = Config(c1, c2, c3)

function function1(someValue, c::Config)
    # do stuff
end

filteredValue = function1(someValue, Config(c2 = 5.0))

Putting it in a struct is probably the best way. Also see

which makes it even easier.

5 Likes

In my case the Config type holds a lot of different types. Only a small part of the large configuration is relevant for the specific functions mentioned above. Unfortunately it is not just one subtype which I can pass to these function.

But my real concern with just passing the whole configuration to the function is the seperation of concern principle: Separation of concerns - Wikipedia
What I mean here is that the function should not be concerned about the structure of the configuration variable. If it is, it is much harder to reuse that code in a different context, where I have a different set of configuration names and values, or no Config type at all.

If you are talking about positional input arguments, just wrap the inputs in a tuple and splat them in:

cfg = (cfg1, cfg2, cfg3, cfg4)
finalValue = function2(filteredValue, cfg...)

If they are named keywords, use a Dict:

cfg = Dict(:cfg1=>blah, :cfg2=>bleh)
finalValue = function2(filteredValue; cfg...)  # note the semicolon
4 Likes

Should note that Dicts are quite slow, and so if you know what the fields will be in advance, you should use a (immutable) type instead. Parameters.jl as mentioned above makes this easy to do. But yes, the choice is really between tuples, types, or Dicts.