Search code examples
typesocamlreusability

OCAML: Parameter with multiple types


Is it possible to have a parameter with multiple types in Ocaml?

I have defined two different types and both of those types have an address field:

type symbol =
  {
    address : string;
    name : string;
  }


type extern_symbol =
  {
    address : string;
    name : string;
    ...
  }

I also have a function which takes a symbol list as parameter and does checks with the address field. Now I'd like to reuse the code of the function for an extern_symbol list. The function would do exactly the same with the other list. Is there a way to get that done without having to write duplicate code?


Solution

  • You can't do this directly with record parameters, as all record types are distinct. There's no type for the notion of "any record with a field named address of type string". Hence you can't have a parameter of that type.

    You could, of course, just pass the address to the function instead of the whole record if that's all you need.

    Or you could pass a function that extracts the address:

    let myfun address_of r =
        do_what_you_want (address_of r)
    
    let internal_addr (r: symbol) = r.address
    let external_addr (r: extern_symbol) = r.address
    
    myfun internal_addr r1
    myfun external_addr r2
    

    So then myfun has a type like this:

    (a -> string) -> a -> result
    

    This generalizes to other operations that can be applied to both record types.

    You can also use object types rather than records. There is a type for the notion of "any object with a method named address that returns a string":

    < address : string; .. >
    

    For example:

    # let myfun2 ob = do_what_i_wanted ob#address;;
    val myfun2 : < address : string; .. > -> string = <fun>
    

    Finally, you could make the two types different variants of the same type:

    type symbol =
       | Internal of { address: string; name: string }
       | External of { address: string; name: string; ... }
    

    Then there would be no problem whatsoever handling them by the same function:

    let myfun3 sym =
        let addr =
            match sym with
            | Internal x -> x.address
            | External x -> x.address
        in
        do_what_i_wanted addr