#! /usr/bin/env ocaml | |
(* From http://keleshev.com/point-free-case-analysis *) | |
open Printf | |
let id x = x | |
let const x _ = x | |
(* | |
# Point-free pattern matching | |
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 | |
(* | |
And 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 for not saying much. | |
That's because pattern-matching has this rigid syntactical structure. | |
An alternative could be is to write a function for case | |
analysis that uses labled 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 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 | |
### 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 ", ") | |
*) | |
end | |
(* | |
As you might noticed the case function is similar to | |
[fold as a recursion scheme](./). | |
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. | |
*) | |
(* | |
# XXX | |
You might consider going from pattern-matching to case function | |
to be a petty improvement. However, there is a different use-case for case. | |
That is do expose the pattern-matching-like facility for abstract types. | |
For example, we can define the following abstract type: | |
*) | |
module Zero_one_many_2 : 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 it 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 our interface, while still delivering a decent | |
tool for case analysis. | |
*) | |
module Parity = struct | |
let case ~even ~odd n = | |
if n mod 2 = 0 then even n else odd n | |
module Test = struct | |
case 42 | |
~even:(fun n -> printf "%d is even\n" n) | |
~odd:(fun n -> printf "%d is odd\n" n); | |
case 42 | |
~even:(printf "%d is even\n") | |
~odd:(printf "%d is odd\n"); | |
end | |
end | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment