Is there any way to define a set of pattern matches separately and then combine such that they match cases are inlined and we recover the performance of the combined match?
As a (contrived) example, let’s say I have a simple arithmetic expression language
type t =
| Neg of t
| Add of t * t
| Mul of t * t
| Lit of string
I want to modify expressions in this language so that -1 * n
is always expressed as ~-n
, 1 * n
is expressed as n
and 0 + n
is always expressed as n
.
Writing this as a single match:
let rec single t =
match t with
| Mul (Lit "-1", n) | Mul (n, Lit "-1") -> manual (Neg n)
| Add (Lit "0", n) | Add (n, Lit "0") -> manual n
| Mul (Lit "1", n) | Mul (n, Lit "1") -> manual n
| _ -> t
;;
I can see, for instance, that the two cases matching on Mul
will be compiled so that we only test for the tag once, as expected:
ocamlopt -dlambda ./single.ml
...
case tag 2:
(let (n/283 =a (field_imm 0 t/274))
(catch
(catch
(catch
(switch n/283
case tag 3:
(stringswitch (field_imm 0 n/283)
case "-1": (exit 1 (field_imm 1 t/274))
default: (exit 8))
default: (exit 8))
with (8)
(let (*match*/293 =a (field_imm 1 t/274))
(switch *match*/293
case tag 3:
(stringswitch (field_imm 0 *match*/293)
case "-1": (exit 1 n/283)
default: (exit 7))
default: (exit 7))))
with (7)
(switch n/283
case tag 3:
(stringswitch (field_imm 0 n/283)
case "1": (exit 3 (field_imm 1 t/274))
default: (exit 6))
default: (exit 6)))
with (6)
(let (*match*/296 =a (field_imm 1 t/274))
(switch *match*/296
case tag 3:
(stringswitch (field_imm 0 *match*/296)
case "1": (exit 3 n/283)
default: (exit 4))
default: (exit 4)))))
case tag 3: (exit 4))
...
Now suppose instead I want to define each rewrite as a separate function and then compose them:
let partial_1 = function
| Mul (Lit "-1", n) | Mul (n, Lit "-1") -> Some (Neg n)
| _ -> None
;;
let partial_2 = function
| Add (Lit "0", n) | Add (n, Lit "0") -> Some n
| _ -> None
;;
let partial_3 = function
| Mul (Lit "1", n) | Mul (n, Lit "1") -> Some n
| _ -> None
;;
let rec composed t =
match partial_1 t with
| Some t -> composed t
| _ ->
(match partial_2 t with
| Some t -> composed t
| _ ->
(match partial_3 t with
| Some t -> composed t
| _ -> t))
;;
Looking at the lambda output, this will never “see through” the function applications and recombine the now separate matches, leading to repeated inspection of tags.
Is there anyway to coerce the compiler into doing this or an alternative way to decompose matches?