# Point-free Pattern Matching

2018-12-18

Sometimes you’ve got a simple variant type, like this one:

```module Zero_one_many = struct
type t =
| Zero
| One of string
| Many of string list
end
```
So you start writing some basic functions for it, like the following ones. Sometimes many of them.
```let to_string = function
| Zero -> "(zero)"
| One s -> s
| Many list -> String.concat ", " list

let count = function
| Zero -> 0
| One _ -> 1
| Many list -> List.length list
```

And it feels like a lot of code that is not saying much. That’s because pattern matching has this rigid syntactical structure. An alternative is to write a function for case analysis that uses labeled parameters, instead of pattern matching:

```let case ~zero ~one ~many = function
| Zero -> zero
| One s -> one s
| Many list -> many list
```

Now we can express the same functions more concisely, primarily because we can use the point-free style:

```let to_string =
case ~zero:"(zero)" ~one:id ~many:(String.concat ", ")

let count =
case ~zero:0 ~one:(const 1) ~many:List.length
```

Compare, there is a symmetry between pattern matching and this style of case analysis using labeled parameters:

### Pattern matching:

```let to_string = function
| Zero -> "(zero)"
| One s -> s
| Many list -> String.concat ", " list
```

### Point-full functional case analysis:

```let to_string =
case
~zero:"(zero)"
~one:(fun s -> s)
~many:(fun list -> String.concat ", " list)
```

### Point-free functional case analysis:

```let to_string =
case ~zero:"(zero)" ~one:id ~many:(String.concat ", ")
```

The difference is that for functions we can perform the η-conversion to make the definition more concise, and in many—but not all!—cases, more readable.

## Is this just fold?

As you might have noticed the case function is similar to fold. Except fold does both case analysis and is a recursion scheme, while case does only case analysis. For non-recursive data types, case and fold are the same function.

## Case for Abstract Types

There is a different use-case for case: to expose a pattern-matching-like facility for abstract types. For example, we can re-define our original `Zero_one_many.t` type as an abstract type with a different implementation:

```module Zero_one_many : sig
type t

val case : zero:'a
-> one:(string -> 'a)
-> many:(string list -> 'a)
-> t
-> 'a

val to_string : t -> string
end = struct
type t = string list

let case ~zero ~one ~many = function
| [] -> zero
| [s] -> one s
| list -> many list

let to_string =
case ~zero:"(zero)" ~one:id ~many:(String.concat ", ")
end
```

We implemented the type as a `string list`, but we could have used our original variant implementation. And that’s the beauty of it. We can go back and forth between implementations without breaking the interface, while still delivering a decent tool for case analysis.

## Case for Concrete Types

Yet another use-case for case is to provide new ways of case analysis for existing concrete types. For example, we can do a case analysis on `int` to decide if it is even or odd:

```module Parity = struct
let case ~even ~odd n =
if n mod 2 = 0 then even n else odd n
end

module Test = struct
Parity.case 42
~even:(fun n -> printf "%d is even\n" n)
~odd:(fun n -> printf "%d is odd\n" n);

Parity.case 42
~even:(printf "%d is even\n")
~odd:(printf "%d is odd\n");
end
```

## Code

The code is available in a GitHub gist.

## Conclusion

The case function is a small and sometimes useful pattern that can simplify some code. If you haven’t yet learned about its more powerfull cousin fold then read my Interpretations of Fold.