Please explain the ppx_variants_conv make_matcher method signature

Viewed 36

I found myself wanting a way to do some codegen around some large variant types in my code, and I found ppx_variants_conv (https://github.com/janestreet/ppx_variants_conv)

The make_matcher method sounds potentially useful to me, but there are no docs and tbh I am struggling to read the example signature:

  val make_matcher :
       a:(('a -> 'a t)         Variant.t -> 'b -> ('c -> 'd)         * 'e)
    -> b:((char -> 'f t)       Variant.t -> 'e -> (char -> 'd)       * 'g)
    -> c:('h t                 Variant.t -> 'g -> (unit -> 'd)       * 'i)
    -> d:((int -> int -> 'j t) Variant.t -> 'i -> (int -> int -> 'd) * 'k)
    -> 'b
    -> ('c t -> 'd) * 'k

a b c and d labelled arguments correspond to the cases of the variant and the first part of each signature corresponds to the constructor for each case... I get a bit lost after that

and it seems a odd to me that 'b 'c 'd and 'k appear in the latter part of the signature but not 'e 'f 'g 'h 'i 'j

1 Answers

In the make_matcher function each labeled argument takes a function of two arguments that has a general form, fun v x -> f, y, where v is the first-class variant that represents the corresponding constructor, x is the value that is folded over all matchers-generating functions. The function returns a pair, in which the first constituent, the function f, is actually the matcher that will be called if that variant matches and some value y that will be passed to the next matcher-generating function.

Let's do some examples to illustrate this. First, let's define some simple matcher, e.g.,

type 'a t =
  | A of 'a
  | B of char
  | C
  | D of int * int
[@@deriving variants]

let matcher init = Variants.make_matcher
    ~a:(fun v (x1 : char) ->
        (fun x -> x+1),Char.code x1)
    ~b:(fun v (x2 : int) ->
        (fun c -> Char.code c),float x2)
    ~c:(fun v (x3 : float) ->
        (fun () -> 0), string_of_float x3)
    ~d:(fun v (x4 : string) ->
        (fun x y -> x + y),[x4])
    init

and here's how we can use, it,

# let f,s = matcher '*';;
val f : int t -> int = <fun>
val s : Base.string list = ["42."]
# f (A 10);;
- : int = 11
# f (B '*');;
- : int = 42
# f C;;
- : int = 0
# f (D (1,2));;
- : int = 3

To be honest, I don't know the purpose of the extra parameter that is passed to each matcher-generating function1. Probably, the idea is that depending on the initial parameter we could generate different matchers. But if you don't need this, then just pass () to it and/or define your own simplified matcher that ignores this additional information, e.g.,

let make_simple_matcher ~a ~b ~c ~d =
  fst @@Variants.make_matcher
    ~a:(fun _ _ -> a,())
    ~b:(fun _ _ -> b,())
    ~c:(fun _ _ -> c,())
    ~d:(fun _ _ -> d,())
    ()

The make_simple_matcher function has an expected type,

a:('a -> 'b) ->
b:(char -> 'b) -> 
c:(unit -> 'b) -> 
d:(int -> int -> 'b) -> 
'a t -> 'b

1) looking into the guts of the code that generates this function doesn't help a lot, as they use the generic name acc for this parameter, which is not very helpful.

Related