Skip to content

Instantly share code, notes, and snippets.

@keleshev 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
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.