Patterns

Wipple uses pattern matching to express control flow. For example, let’s say we want to generate a report card:

Grade : type {
    A
    B
    C
    D
    F
}

report-card :: Grade -> Text
report-card : grade -> when grade {
    A -> "top of the class"
    B -> "good work"
    C -> "need to study"
    D or F -> "didn't pass"
}

show (report-card A)
top of the class

First, we define our patterns using type. Rather than providing fields, we list the variants, and Wipple will create an enumeration for us. Then, we use when to return a different value for each variant. You can use or to match multiple variants at once.

In fact, in Wipple, if is just a regular function that matches on Boolean. We can create our own easily:

My-Boolean : type {
    My-True
    My-False
}

my-if : bool then else -> when bool {
    My-True -> do then
    My-False -> do else
}

show (my-if My-True {123} {456})
123

In addition to enumerations like these, you can store data alongside each pattern, allowing you to express values that are tied to a condition — in other words, the value is “wrapped” in a pattern, and you need to “unwrap” the value by checking for the condition using when. This may sound a bit confusing if you’ve used other languages without this feature, so let’s look at an example:

Maybe-Number : type {
    Some-Number Number
    No-Number
}

Here, we create a Maybe-Number value with two patterns. The first pattern contains a Number, and the second pattern contains nothing. Now, we can use pattern matching to “unwrap” the Maybe-Number:

Maybe-Number : type {
    Some-Number Number
    No-Number
}
describe-maybe-number : maybe -> when maybe {
    Some-Number n -> "we have a number: _" n
    No-Number -> "we don't have a number"
}

show (describe-maybe-number (Some-Number 42))
show (describe-maybe-number No-Number)
we have a number: 42
we don't have a number

Why is this useful? It means we can represent errors in our program! Let’s go back to our report card example, and allow the user to specify a grade as input:

Grade : type {
    A
    B
    C
    D
    F
}

report-card :: Grade -> Text
report-card : grade -> when grade {
    A -> "top of the class"
    B -> "good work"
    C -> "need to study"
    D or F -> "didn't pass"
}
Maybe-Grade : type {
    Valid-Grade Grade
    Invalid-Grade
}

parse-grade :: Text -> Maybe-Grade
parse-grade : text -> when text {
    "A" -> Valid-Grade A
    "B" -> Valid-Grade B
    "C" -> Valid-Grade C
    "D" -> Valid-Grade D
    "F" -> Valid-Grade F
    _ -> Invalid-Grade
}

repeat forever {
    grade : parse-grade (prompt "Enter your grade")

    when grade {
        Valid-Grade g -> show (report-card g)
        Invalid-Grade -> show "invalid grade"
    }
}
Enter your grade: A
top of the class
Enter your grade: B
good work
Enter your grade: Z
invalid grade
...

Wipple’s type system will check for us that we handle the error — watch what happens if we pass our Maybe-Grade to report-card directly:

Grade : type {
    A
    B
    C
    D
    F
}

report-card :: Grade -> Text
report-card : grade -> when grade {
    A -> "top of the class"
    B -> "good work"
    C -> "need to study"
    D or F -> "didn't pass"
}

Maybe-Grade : type {
    Valid-Grade Grade
    Invalid-Grade
}

parse-grade :: Text -> Maybe-Grade
parse-grade : text -> when text {
    "A" -> Valid-Grade A
    "B" -> Valid-Grade B
    "C" -> Valid-Grade C
    "D" -> Valid-Grade D
    "F" -> Valid-Grade F
    _ -> Invalid-Grade
}
grade : parse-grade (prompt "Enter your grade")
show (report-card grade)
example:32:19: error: expected `Grade` here, but found `Maybe-Grade`

If you’ve used a language with exceptions, Wipple’s pattern matching is kind of like try...catch, but you are forced to handle every error explicitly. This can seem cumbersome at first, but it makes bugs much easier to track down. And don’t worry, you don’t have to define your own Maybe type every time — Wipple has one built in that works for any type! We’ll learn how to use it in the next chapter.