7
$\begingroup$

Suppose I want to define a function whose argument must be a List of only Strings and List must have at least one member. I know how to require that the Head must be a List but how does one qualify List members within an argument? I really don't want to resort pattern matching inside the body of my definitions or trailing a conditional pattern outside my definitions.

This is what I have so far along with a few test cases...

fn[arg_List && AllTrue[arg, StringQ] && Length@arg > 0] := 
  Block[{}, arg];
fn[{"a", "b", "c"}] (* should return {"a","b","c"} *)
fn[{1, "b", "c"}]
fn[{}]

Unfortunately it produces no errors which in this case would have been very helpful.

$\endgroup$
4
  • 1
    $\begingroup$ does it work if you replace it with fn[arg_List /; (AllTrue[arg, StringQ] && Length@arg > 0)] := Module[{}, arg]; ? btw, why Block there? Why not use normal Module? $\endgroup$
    – Nasser
    Oct 28, 2021 at 8:11
  • $\begingroup$ @Nasser thank you that worked!!! i try not to use modules unless absolutely necessary because they require more memory and are slower than blocks. $\endgroup$ Oct 28, 2021 at 8:18
  • 1
    $\begingroup$ because they require more memory and are slower than blocks I did not know this. I use Modules all the time, since Block scoping is a little confusing to me. But if they work for you, that should work then. $\endgroup$
    – Nasser
    Oct 28, 2021 at 8:20
  • 2
    $\begingroup$ You could also do fn[arg_ /; VectorQ[arg, StringQ] && arg != {}] := ... $\endgroup$
    – Roman
    Oct 28, 2021 at 8:46

1 Answer 1

11
$\begingroup$

When imposing restrictions on patterns, there are typically two options:

  • Use Condition (/;) to specify an arbitrary expression as test. (This is what was shown in the comments)

    fn[arg_List /; (AllTrue[arg, StringQ] && Length@arg > 0)] := Module[{}, arg];
    fn[arg_ /; VectorQ[arg, StringQ] && arg != {}] := Module[{}, arg];
    

    This is most flexible, but can be significantly slower, since the pattern matcher has to ask the kernel to evaluate the condition

  • Use structural patterns: The pattern arg_List is mostly equivalent to arg:{___}, where Pattern (:) gives the name, and the {___} is "anything that looks like a list with zero or more arguments (see BlankNullSequence, ___). To require at least one element, use BlankSequence (__). You can also specify a head for these elements, e.g. __String. So the pattern here would be:

    fn[arg:{__String}] := Module[{}, arg];
    

    If each element should follow a more complex pattern, you can use Repeated (..) and RepeatedNull (...). So to require e.g. a list of lists of strings, use

    fn[arg:{{__String}...}] := Module[{}, arg];
    

    If the condition on the single elements is more complex, you can use PatternTest (?):

    fn[arg:{__String?(StringStartsQ["a"])}] := Module[{}, arg];
    

    This example requires a list of strings starting with a.

$\endgroup$
3
  • $\begingroup$ you really kicked ass on this answer. you dispel many mysteries behind conditional patterns for arguments that should have been better explained in the official documentation. $\endgroup$ Oct 28, 2021 at 11:24
  • $\begingroup$ if i wanted this fn[arg:{__String}] but wanted to require all strings have StringLength > 0 not sure how to add that. $\endgroup$ Nov 11, 2021 at 16:12
  • $\begingroup$ @JulesManson I would do it similar to the last example, so e.g. (StringLength[#]>0&) instead of (StringContainsQ["a"]) $\endgroup$
    – Lukas Lang
    Nov 11, 2021 at 23:11

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.