Last active
December 18, 2018 21:41
-
-
Save keleshev/a7e28bb2163e2a88da60d44d03169b2d to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#! /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