Bob Fang
Bob Fang

Reputation: 7411

OCaml: How to handle sum type properly?

suppose I have the following code:

type s = A of a | B of b


let foo (a:?) = 

let bar (input:s) = match input with
| A a -> foo input
| B b -> ...

My question is what should I fill in the question mark in the signature of foo so I won't need a (redundant) match statement in it? Or is there a better pattern to do this?

Upvotes: 5

Views: 1404

Answers (4)

ivg
ivg

Reputation: 35280

Starting with your example, the solution would be simple:

type s = A of a | B of b


let foo (a:a) = 

let bar (input:s) = match input with
| A a -> foo a
| B b -> ...

But constraint here is not needed. Looks like that you're misunderstanding the idea of type constraints. In general, in OCaml type constraints cannot affect the program. Programs with and without type constraints have the same behavior. So, here you don't need to put any constraints at all. You must think of type annotations only as a helper tool for programmer.

Update

I'm still not sure, that I understand what actually you want, but if you want to split your variants into subsets, and keep this split refutable, then, indeed, you can use polymorphic variants, as Pascal suggested.

Let me first rephrase the questions. Suppose I have type:

  type t = A | B | C | D | E

and I have a pattern match

  let f = function
    | A | B | C as x -> handle_abc x
    | D | E as x -> handle_de x

How can I prove to a compiler, that handle_abc takes only a subset of all possible constructors, namely A | B | C ?

The answer is, with regular variants it is impossible. But it is possible with polymorphic variants:

type t = [`A | `B | `C | `D | `E]

let f = function
  | `A | `B | `C as x -> handle_abc x
  | `D | `E as -> handle_de x

So, handle_abc now needs only to pattern match on three variants, and don't need to have any redundant matches. Moreover, you can give names to a groups of constructors, and pattern match on this names:

type abc = [`A | `B | `C ]
type de  = [`D | `E ]
type t = [ abc | de ]

let f = function
  | #abc as x -> handle_abc x
  | #de as -> handle_de x

As a real world example, you can take a look at BAP project where polymorphic variants are used to represent instruction code. Here we split all codes into different subgroups, like all move instructions, all branch instructions and so on. And later we can pattern match on the groups directly.

Upvotes: 7

didierc
didierc

Reputation: 14750

If you want to avoid rematch, I see 3 solutions:

  1. have the function foo simply take the "payload" of the value constructor A, and reconstruct a value of type s as its output (or any other type matching the output type of bar).

    # type a;;
    type a
    # type b;;
    type b
    
    
    # module Ex1 = struct
    
      type s = A of a | B of b
    
    
      let foo (a:a) = A a
    
      let bar (input:s) = match input with
      | A a -> foo a
      | B b -> (* ... *) input
    end;;
    
    module Ex1 :
    sig
      type s = A of a | B of b 
      val foo : a -> s 
      val bar : s -> s 
    end
    
  2. use polymorphic variants:

    # module Ex2 = struct
    
      type s = [ `A of a | `B of b ]
    
      let foo (`A a) = `A a
    
      let bar (input:s) = match input with
        | `A _ as a -> foo a
        | `B b -> (* ... *) input
     end;;
     module Ex2 :
     sig
       type s = [ `A of a | `B of b ]
       val foo : [< `A of 'a ] -> [> `A of 'a ]
       val bar : s -> s
     end
    
  3. use GADTs:

    # module Ex3 = struct
    
      type _ s = 
      | A : a -> a s 
      | B : b -> b s
    
    
      let foo (a: a s) : a s = 
        match a with
        | A a -> A a
    
      let bar: type x.  x s -> x s = fun input ->
        match input with
        | A _ as a -> foo a
        | B b -> (* ... *) input
    
     end;;
     module Ex3 :
     sig
       type _ s = A : a -> a s | B : b -> b s
       val foo : a s -> a s
       val bar : 'x s -> 'x s
     end
    

Upvotes: 9

Ionuț G. Stan
Ionuț G. Stan

Reputation: 179219

One solution, that incurs a runtime cost, would be to have the variants wrap tuples instead of individual values. Then it's easier to capture the whole tuple and send it to a specialized function:

type s =
  (* Note the extra parentheses! *)
  | Foo of (int * string)
  | Bar of (char * int * string)

let foo (i, s) = "foo"

let bar (c, i, s) = "bar"

let main input =
  match input with
  | Foo f -> foo f (* `f` is bound to a tuple of type `int * string` *)
  | Bar b -> bar b (* `b` is bound to a tuple of type `char * int * string` *)

Upvotes: 2

matrixanomaly
matrixanomaly

Reputation: 6967

You would have to fill in the type in the question mark for the signature of Foo, and then use a match statement in it. The place where the question mark is denotes a type. In a way it is assisting the compiler by telling it what exact type you want, and it will strictly ensure that operations you carry out on a or input is of a matching type.

The match statement is not that redundant and does not hurt performance much as it is very efficient in OCaml. However we have another approach as below.

Alternatively if you only have one parameter, you could save some typing by doing function in place of match. For example:

let foo (c:s) = match c with ....

we can do

let foo  = function 
  | A -> ...
  | B -> ...

Note that the function word will only work if you have one parameter passed in (you could definitely wrap up all your parameters into a list and pass it in if you like)

Here's an additional example to get my point across:

type number =  |Int  of int
               |Float of float

let to_int: (number -> int option) = function
| Int n -> Some n
| _ -> None 

(*this is the same as *)  (*also note that int option denotes return type*)

let to_int (input:number) : int option = 
 match input with 
 | Int n -> Some n 
 | _ -> None  

(*notice how the first one does not have a input as a parameter name*)
let n1:number = Int 1;;
let n2:number = Int 2;;

Example use: `to_int(n1);`

Just to be clear, there isn't a need to fill it in, and type assists helps the programmer as well, and for me in some ambiguous cases helped make sure the compiler knew what I wanted. According to my professor a few semesters ago, it is a good practice to explicitly mention it to keep types in check.

Upvotes: 1

Related Questions