# Use Associations as function arguments?

Posted 6 months ago
573 Views
|
|
1 Total Likes
|
 The problemIt is at times necessary to create functions that take a large number of arguments. This can make using and refactoring such functions awkward, as the Wolfram Language does not have built-in named parameters, instead relying on position. For example, one can define a function pets[catName_String,dogName_String]:=(*body*) but if your cat is named Felix, and your dog is Rover, then the function must always be called pets["Felix","Rover"] and never pets["Rover","Felix"]. Not much of a problem for two arguments, but if there are many, it can make for bug prone code.Associations give us a neat way of implementing named arguments. We could redefine the above function as pets[petNames_Association]:=(*body*) and call it using an association e.g. pets[<|"dog" -> "Felix", "cat" -> "Rover"|>], and it will now not matter which order we pass the pets' names in, as petNames["cat"] and petNames["dog"] in the function body will return the correct pet name.However in solving one problem we have introduced another: it is now no longer clear from the left hand side of the function what the arguments should be (beyond being supplied as an Association). This could be a real problem for anyone else using the code. Furthermore we can no longer use pattern matching to restrict the 'types' of arguments - e.g. we could call this function with pets[<|"dog" -> 1.234, "cat" -> 5.678|>] and it would attempt to evaluate.The solutionWith a bit of experimenting, I've found what I think is a nice way of implementing named function parameters using associations, in such a way as to make the parameter names explicit, and also allow for pattern matching on the values. First define a function: AssociationContaining[a_Association, keyPatterns : {(_String -> (_Pattern | _PatternTest | _Blank)) \ ..}] := MatchQ[a, KeyValuePattern[keyPatterns]] ... and then make a second definition that makes use of 11.3's new Curry function: AssociationContaining[ keyPatterns : {(_String -> (_Pattern | _PatternTest | _Blank)) ..}] := Curry[AssociationContaining][keyPatterns] AssociationContaining is a predicate function (i.e. returns True or False) that takes an association, and a list of keys and value-patterns, and uses the built-in MatchQ and KeyValuePattern to determine whether the given association contains all of the key/pattern pairs in the list. And now we can use AssociationContaining in its curried form to implement pattern matching on named function arguments, for example: pets[petNames_?(AssociationContaining[{"cat" -> _String, "dog" -> _String}])] := (*body*) We can access the argument values in the function body as expected, i.e. petNames["cat"] and petNames["dog"]. But now the function will only attempt to evaluate if the supplied association contains "cat" and "dog" keys, and both are strings. So the function call pets[<|"dog" -> "Rover", "cat" -> "Felix" |>] will evaluate, as will pets[<|"cat" -> "Felix", "dog" -> "Rover" |>] However attempting to call pets[<|"cat" -> 1.234, "dog" -> "Rover"|> (* OR *) pets[<|"cat" -> "Felix" |>] will not evaluate, thanks to the pattern matching restrictions placed on the arguments.I hope this proves useful to someone. I had great fun playing around with this, and for me it is another illustration of how powerful and flexible the Wolfram Language is.