TIL: Gleam Pattern Matching and Pipe
It has been slightly more than two years since I have written any Elm. Plus with the advancement of AI-assisted coding, I have not really written much code by hand over the past year.
I decided to give Exercism a try. In particular, I picked up the Gleam track. I wanted to note down these two exercises which I just did. As someone who did Elm before, I felt super rusty not knowing how to implement but did them in a noob way.
Guessing Game
This exercise teaches pattern matching. Implement the function that will, given an integer, return:
- 42? “Correct”
- 41 or 43? “So close”
- Less than 41? “Too low”
- More than 43? “Too high”
My initial implementation worked:
pub fn reply(guess: Int) -> String {
case guess {
42 -> "Correct"
41 | 43 -> "So close"
i if i < 41 -> "Too low"
i if i > 43 -> "Too high"
_ -> "Too high"
}
}
But it left me pondering: why do I need to _ -> "Too high"
just to satisfy the compiler? In Elm, usually I would avoid using _ ->
for pattern matching because rarely I will pattern match on integers or strings. In this exercise, however, we are working with an integer. Without the _ ->
, the compiler for Gleam complains:
error: Inexhaustive patterns
┌─ src/guessing_game.gleam:2:3
│
2 │ ╭ case guess {
3 │ │ 42 -> "Correct"
4 │ │ 41 | 43 -> "So close"
5 │ │ i if i < 41 -> "Too low"
6 │ │ i if i > 43 -> "Too high"
7 │ │ }
│ ╰───^
This case expression does not have a pattern for all possible values. If it
is run on one of the values without a pattern then it will crash.
The missing patterns are:
_
Then, I went with something like:
pub fn reply(guess: Int) -> String {
case guess {
42 -> "Correct"
i if i < 41 -> "Too low"
i if i > 43 -> "Too high"
_ -> "So close"
}
}
Slightly better.
Log Levels
This exercise was to implement three functions which will print out parts of a given log line or reformat the log line. However, for brevity, I will stick to just one function. For example, given "[ERROR]: This is an error"
, either:
- Return just the error message:
"This is an error"
- Return the the log level:
"error"
- Or reformat the log:
"This is an error (error)"
My initial implementation was:
pub fn message(log_line: String) -> String {
case log_line {
"[INFO]: " <> msg -> string.trim(msg)
"[WARNING]: " <> msg -> string.trim(msg)
"[ERROR]: " <> msg -> string.trim(msg)
_ -> "Unknown"
}
}
The repetitive string.trim()
annoyed me a little. Then I remembered that since these can be pure functions, whatever is the last line will be returned. So, I can do something like this with a pipe:
pub fn message(log_line: String) -> String {
case log_line {
"[INFO]: " <> msg -> msg
"[WARNING]: " <> msg -> msg
"[ERROR]: " <> msg -> msg
_ -> "Unknown"
}
|> string.trim
}
Slightly better. This will read as “pattern match, take the result, pipe it to a string.trim function”.