Instantly share code, notes, and snippets.

@keleshev /case.ml
Last active Dec 18, 2018

Embed
What would you like to do?
#! /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