I think it would really be valuable for you to look at intermediate results, maybe use Trace, and come up with test cases that are both simpler and more robust. Side note, I really don't understand the desire to use Module here at all.
Anyway, just based on your original description,
a function which can accept a list or separate elements as arguments
I would have come up with this:
myFunc[list_List] := processTheStuff[list];
myFunc[args__] := myFunc[{args}]
I assume that you want the function to behave "the same" for each type of argument. My standard way of doing that is to choose which form is canonical, and use that form in the definition of the other. In this case, it seemed simpler to make the list form canonical.
Now, why does this even work at all? Because the Mathematica evaluator looks at the DownValues for a function in a particular order. It looks from most specific pattern to least specific pattern. The heuristic for "specificity of pattern" isn't always obvious (and in tie-breaker situations the definition evaluated first comes before the one evaluated later), but you can always just look at the DownValues and know that they will be inspected in that order. In this particular case, myFunc[list_List]
is unambiguously more specific than myFunc[args__]
.
Let's test it. Here is where I think you get yourself in trouble. Start your testing with simple examples.
myFunc[a] (* processTheStuff[{a}] *)
myFunc[{a}] (* processTheStuff[{a}] *)
myFunc[a, b] (* processTheStuff[{a, b}] *)
myFunc[{a, b}] (* processTheStuff[{a, b}] *)
myFunc[{a}, b] (* processTheStuff[{{a}, b}] *)
myFunc[{a}, {b}] (* processTheStuff[{{a}, {b}}] *)
myFunc[{{a}, {b}}] (* processTheStuff[{{a}, {b}}] *)
(* and so on until you're satisfied *)
Side note, I'm dismissing the whole Flatten stuff, because that would fundamentally change the structure of your inputs, and so we wouldn't get the same behavior for each type of argument. If you need Flatten, that suggests a completely different question, or at least it seems that way to me.
Okay, let's try to answer your latest question. Mutating one of the definitions as you did is a good way to help understand the pattern matching. I'd be even more explicit about it. Let's redefine myFunc
:
ClearAll[myFunc];
myFunc[list_List] := processA[list];
myFunc[args__] := processB[{args}];
And here's your test input:
args = {{a}, {b}, {c}}
Now let's test:
myFunc[args] (* processA[{{a}, {b}, {c}}] *)
myFunc[{a}, {b}, {c}] (* processB[{{a}, {b}, {c}}] *)
So that's pretty clear.
What about args // Sequence
? Well, I think you think this is doing something different than it is. This is just wrapping args in the head Sequence
. When Sequence
expressions are encountered "inside" other expressions, the Sequence
effectively gets stripped and you're left with the ... well, sequence that was inside Sequence
.
args // Sequence (* Sequence[{{a}, {b}, {c}}] *)
JustToTest[args // Sequence] (* JustToTest[{{a}, {b}, {c}}] *)
myFunc[args // Sequence] (* processA[{{a}, {b}, {c}}] *)
What you might have thought you were doing was replacing the List
head with Sequence
. But you do that with Apply
, aka @@
Sequence @@ args (* Sequence[{a}, {b}, {c}] *)
JustToTest[Sequence @@ args] (* JustToTest[{a}, {b}, {c}] *)
myFunc[Sequence @@ args] (* processB[{{a}, {b}, {c}}] *)