Search code examples
pattern-matchingocamlvariant

Pattern matching against the number of arguments of a constructor


I'd like to create a function inc that works as follows:

type operation = MulNeg | Add n | Sub n
let ops = [Add 3; Add 4; Sub 2; MulNeg] in
let inc l = ... in
inc ops (* should return [Add 4; Add 5; Sub 3; MulNeg] *)

I know I can implement it like this:

let inc l = List.map (function 
    | MulNeg -> MulNeg
    | Add a -> Add (a + 1)
    | Sub a -> Sub (a + 1)
  ) l

However in my program there are many more operations, therefore my goal would be to have a function that acts according to the number of arguments that the constructor has. The closest I've gotten to is this:

let inc l = List.map (function
    | op a -> op (a + 1)
    | op -> op
  ) l

However that raises an error on the op a. Is there a legal way to make such a pattern ?


Solution

  • You can not pattern match on the number of constructor parameters. However, a common solution to your problem would be to introduce an extra indirection, e.g.,

    type binop = Add | Sub | Mul | Div
    type unop = Inc | Dec | Ref
    type exp = 
      | Const of int
      | Var of string
      | Unop of unop * exp
      | Binop of binop * exp * exp
    
    let rec incr = function
      | Const x -> Const (x+1)
      | Var _ as v -> v
      | Binop (op,x,y) -> Binop (op,incr x, incr y)
      | Unop (op,x) -> Unop (op, incr x)
    

    This won't scale, of course, when you have constructors with an arbitrary number of arguments. Then you either need to change the representation or stick to some other abstraction. For example, a Lisp-style language could be encoded as follows,

    type prim = Add | Sub | Mul | Div
    type op = Fun of string | Prim of prim
    type exp = 
      | App of op * exp list
      | Atom of string
    

    If both solutions do not suit you, then you shouldn't use algebraic data types, and instead, stick to the visitor pattern.