Function pattern to support listed and non-listed arguments [duplicate]

I often want to write functions that take as an argument either a) a rule or b) a list of rules. As an example, when using Replace with just one rule, it does not matter whether you give it as a list or not:

Replace[Range[10], 7->"seven", {1}]
Replace[Range[10], {7->"seven"}, {1}]


will both work as expected.

Now, if I wanted to set this up, I would use the listed form throughout the function body, and add a second function pattern to actually rewrite the non-listed Rule into a List:

Replace[expr_, rules_Rule, levelspec_] := Replace[expr, {rules}, levelspec]
Replace[expr_, rules_List, levelspec_] := the function body...


Is there a way to write a pattern which matches both forms and returns the listed one?

It is, of course, not too hard to write the function body in a way that it supports both forms. But actually I would like to have it done in the pattern already, so I will not have to take special care of it and can easily extend existing functions to support this feature.

-

marked as duplicate by Mr.Wizard♦Nov 6 '14 at 11:03

I use the same approach that you describe. I think it's the best. Of course it's very difficult to prove that something is not possible, so let's wait :-) It is possible to use a single pattern as in _Rule | {__Rule} but this will shift handling the list-vs-nonlist problem elsewhere, so it doesn't simplify things. I prefer the solution you describe, i.e. f[r_Rule] := f[{r}]. – Szabolcs Jun 20 '14 at 14:53
Well, when I try to use x_Rule|x__Rule, I get attern::patv: Name x used for both fixed and variable length patterns. as an error. – Theo Tiger Jun 20 '14 at 14:58
Exactly. You need to use r: (_Rule | {__Rule}) then in the body of your function check if r is a list of rules or a single rule. This is a single pattern, but it makes things more complicated. This is why I think that your solution is the best alternative. – Szabolcs Jun 20 '14 at 15:00
You may be able to use @Szabolcs's pattern and just wrap it with a list by default if you are using the rules for pattern matching. Mathematica will unwrap lists of rules in most situations, but not always. Try: Range@10 /. {7 -> "seven", 8 -> "eight"} and Range@10 /. {{7 -> "seven", 8 -> "eight"}} and Range@10 /. {{{7 -> "seven", 8 -> "eight"}}} for example. – mfvonh Jun 20 '14 at 16:00
The patv message is not an error. It is simply a warning. The definition is still made as requested and you can choose to ignore the message if you wish. – Oleksandr R. Jun 21 '14 at 12:38

Since you are specifically interested in pattern matching rather than functional equivalents such as the one presented by eldo here is an additional answer.

Szabolcs already gave my preferred solution, which is to use Alternatives, though he did not recommend it. Nevertheless I do. As complete code for reference:

foo[rules : _Rule | {__Rule}] := bar[rules]

foo[1 -> 2]

foo[{1 -> 2, "a" -> "b"}]

bar[1 -> 2]

bar[{1 -> 2, "a" -> "b"}]


You can usually use this as-is because as you note System functions are designed to accept both forms. If for whatever reason you need to "use the listed form throughout the function body" it's as simple as:

Off[Pattern::patv]

ClearAll[foo]
foo[rules_Rule | {rules___Rule}] := bar[{rules}]

foo[1 -> 2]

foo[{1 -> 2, "a" -> "b"}]

bar[{1 -> 2}]

bar[{1 -> 2, "a" -> "b"}]


Note that I turned off the Pattern::patv Message. As Oleksandr correctly notes that Message does not indicate an error but merely serves to indicate a possible mistake; I see it as little more than "training wheels" for new users. Once you understand what it is warning against I suggest you turn it off as it may otherwise keep you from using Mathematica patterns to their full potential.

A final note: don't forget to allow for RuleDelayed rules in your function definitions if you wish to make them general.

A simpler question of the same type:

-
Good explanation, thank you! – Theo Tiger Nov 6 '14 at 10:49
@Theo You're welcome. Thanks for the Accept. – Mr.Wizard Nov 6 '14 at 10:50
@Theo I added links to a couple of earlier answers of mine that I think you should read as well. – Mr.Wizard Nov 6 '14 at 11:10
replace[expr_, levelspec_, rules__] := Replace[expr, List @ rules, levelspec]

replace[Range @ 10, {1}, 7 -> "seven"]
replace[Range @ 10, {1}, 7 -> {"seven"}]

replace[Range @ 10, {1}, 7 -> "seven", 8 -> "eight"]
replace[Range @ 10, {1}, {7 -> "seven", 8 -> "eight"}]


All give the expected results.

-
I didn't edit your bracketing. That edit was made by YOU. Look at the edit history. – m_goldberg Jun 20 '14 at 22:35
@m_goldberg - So sorry, you're right – eldo Jun 20 '14 at 22:44
Thank you for your contribution. However, your code does not address the essential part of the question, i.e. how to support listed and non-listed arguments in a simple manner. – Theo Tiger Jun 21 '14 at 14:35