Skip to content

Latest commit

 

History

History
226 lines (164 loc) · 5.08 KB

Pattern-Matching.md

File metadata and controls

226 lines (164 loc) · 5.08 KB

Pattern Matching

Pattern matching deconstructs a value to bring zero or more expressions into scope. Pattern matches are introduced with the case keyword.

Pattern matches have the following general form:

case value of
  pattern -> result
  ...
  pattern -> result

Pattern matching can also be used in the declaration of functions, as we have already seen:

fn pattern_1 ... pattern_n = result

Patterns can also be used when introducing functions. For example:

example x y z = x * y + z

The following forms can be used for matching:

  • Wildcard patterns
  • Literal patterns
  • Variable patterns
  • Array patterns
  • Constructor patterns
  • Record patterns
  • Named patterns

Guards and pattern guards are also supported.

The exhaustivity checker will introduce a Partial constraint for any pattern which is not exhaustive. By default, patterns must be exhaustive, since this Partial constraint will not be satisfied. The error can be silenced, however, by adding a local Partial constraint to your function.

Wildcard Patterns

The wildcard _ matches any input and brings nothing into scope:

f _ = 0

Literal Patterns

Literal patterns are provided to match on primitives:

f true = 0
f false = 1

g "Foo" = 0
g _ = 1

h 0 = 0
h _ = 1

Variable Patterns

A variable pattern matches any input and binds that input to its name:

double x = x * 2

Array Patterns

Array patterns match an input which is an array, and bring its elements into scope. For example:

f [x] = x
f [x, y] = x * y
f _ = 0

Here, the first pattern only matches arrays of length one, and brings the first element of the array into scope.

The second pattern matches arrays with two elements, and brings the first and second elements into scope.

Constructor patterns

Constructor patterns match a data constructor and its arguments:

data Foo = Foo String | Bar Number Boolean

foo (Foo s) = true
foo (Bar _ b) = b

Record Patterns

Record patterns match an input which is a record, and bring its properties into scope:

f { foo: "Foo", bar: n } = n
f _ = 0

Nested Patterns

The patterns above can be combined to create larger patterns. For example:

f { arr: [x, _], take: "firstOfTwo" } = x
f { arr: [_, x, _], take: "secondOfThree" } = x
f _ = 0

Named Patterns

Named patterns bring additional names into scope when using nested patterns. Any pattern can be named by using the @ symbol:

f a@[_, _] = a
f _ = []

Here, in the first pattern, any array with exactly two elements will be matched and bound to the variable a.

Guards

Guards are used to impose additional constraints inside a pattern using boolean-valued expressions, and are introduced with a pipe after the pattern:

evens :: List Int -> Int
evens Nil = 0
evens (Cons x xs) | x `mod` 2 == 0 = 1 + evens xs
evens (Cons _ xs) = evens xs

When using patterns to define a function at the top level, guards appear after all patterns:

greater x y | x > y = true
greater _ _ = false

To be considered exhaustive, guards must clearly include a case that is always true. Even though the following makes perfect sense, the compiler cannot determine that is is exhaustive:

compare :: Int -> Int -> Ordering
compare x y
    | x > y  = GT
    | x == y = EQ
    | x < y  = LT

Either of these will work, since they clearly include a final case:

compare x y
    | x > y = GT
    | x < y = LT
    | otherwise = EQ

compare x y | x > y = GT
compare x y | x < y = LT
compare _ _ = EQ

(The name otherwise is a synonym for true and is commonly used in guards.)

Guards may also be used within case expressions, which allow for inline expressions. For example, these are equivalent:

fb :: Int -> Effect Unit
fb = log <<< case _ of
  n
    | 0 == mod n 15 -> "FizzBuzz"
    | 0 == mod n 3 -> "Fizz"
    | 0 == mod n 5 -> "Buzz"
    | otherwise -> show n
fb :: Int -> Effect Unit
fb n = log x
  where
  x
    | 0 == mod n 15 = "FizzBuzz"
    | 0 == mod n 3 = "Fizz"
    | 0 == mod n 5 = "Buzz"
    | otherwise = show n

Pattern Guards

Pattern guards extend guards with pattern matching, notated with a left arrow. A pattern guard will succeed only if the computation on the right side of the arrow matches the pattern on the left.

For example, we can apply a function fn to an argument x, succeeding only if fn returns Just y for some y, binding y at the same time:

bar x | Just y <- fn x = ... -- x and y are both in scope here

Pattern guards can be very useful for expressing certain types of control flow when using algebraic data types.

You can also use commas to add multiple expressions in a guard:

positiveLessThanFive :: Maybe Int -> Boolean
positiveLessThanFive mInt
  | Just x <- mInt
  , x > 0
  , x < 5 = true
  | otherwise = false