Search code examples
functional-programmingocamlmonads

OCaml Addable Numbers


I'm learning OCaml, from a little bit of Haskell that I studied for my thesis, and I'm wondering if there is a similar mechanism (type classes) to expresses that two numbers are addable (like a generic for int, float, string, etc). I know that in OCaml type classes don't exist but I'm wondering for similar mechanism to express the same thing

I'm trying to make a module type that have the common "method" add, but without success.

module type Addable = sig
type t
  val add : t -> t -> t 
  val zero : t
  val one : t
end

module Addable_int : Addable with type t = int = struct
  type t = int
  let add a b = a + b
  let zero = 0
  let one = 1
end

module Addable_float : Addable with type t = float = struct
  type t = float
  let add a b = a +. b
  let zero = 0.
  let one = 1.
end

let rec sum (type a) (module A : Addable with type t = a) =
  match l with
  | [] -> A.zero
  | x::l -> A.add x (sum (module A) l)

Update: The function sum give an error: "This expression has type 'a but an expression was expected of type a The type constructor a would escape its scope"

Update-Update:

module type NumberType = sig
type t
  val add : t -> t -> t
  val zero : t
  val one : t
end

module IntNumber : NumberType with type t = int = struct
  type t = int
  let add x y = x + y
  let zero = 0
  let one = 1
end

module FloatNumber : NumberType with type t = float = struct
  type t = float
  let add x y = x +. y
  let zero = 0.0
  let one = 1.0
end

module Addbl (M: NumberType) = struct
  type t = M.t
  let add x y = M.add x y
  let zero = M.zero
  let one = M.one
end

let rec sum : type a. (module NumberType with type t = a) -> a list -> a = 
  fun (module M) l ->
    match l with
    | [] -> M.zero
    | x::t -> M.add x (sum (module M) t)

let () = 
  let xs = [1.;2.;3.] in
  let _ = print_float @@ sum (module FloatNumber) xs in
  let _ = print_string "\n" in
  let xsi = [1;2;3;4] in
  print_int @@ sum (module IntNumber) xsi

This code work and the sum function is generalized. I don't know if there is a way to eliminate the need to pass (module IntNumber) or (module FloatNumber) in the sum function, to abstract this behaviour


Solution

  • No, OCaml does not have type classes.

    But, consider the Float and Int modules both implement several common functions, like add, sub, mul, div, etc.

    They also both have a type t and implement these functions in terms of t. This allows for a functor to abstract out these behaviors and work much the same way.

    # module type NumberType = sig
            type t
        val add : t -> t -> t
        val sub : t -> t -> t
        val mul : t -> t -> t
        val div : t -> t -> t
      end
    
      module Math (N : NumberType) = struct
        type t = N.t
        let ( + ) = N.add
        let ( - ) = N.sub
        let ( * ) = N.mul
        let ( / ) = N.div
      end;;
    module type NumberType =
      sig
        type t
        val add : t -> t -> t
        val sub : t -> t -> t
        val mul : t -> t -> t
        val div : t -> t -> t
      end
    module Math :
      functor (N : NumberType) ->
        sig
          type t = N.t
          val ( + ) : t -> t -> t
          val ( - ) : t -> t -> t
          val ( * ) : t -> t -> t
          val ( / ) : t -> t -> t
        end
    # let open Math (Int) in
      45 + 67;;
    - : int = 112
    # let open Math (Float) in
      45.6 + 87.2;;
    - : float = 132.8
    

    An example with an Addable type using functors:

    # module type Addable = sig
        type t
        val nil : t
        val add : t -> t -> t
      end;;
    module type Addable =
      sig type t val nil : t val add : t -> t -> t end
    # module SumList (A : Addable) = struct
        type t = A.t
        let sum_list = List.fold_left A.add A.nil
      end;;
    module SumList :
      functor (A : Addable) ->
        sig type t = A.t val sum_list : t list -> t end
    # let open SumList (struct include Int let nil = 0 end) in
      sum_list [1; 2; 3; 4];;
    - : int = 10
    # let open SumList (struct 
        include String 
        let nil = "" 
        let add = (^) 
      end) in
      sum_list ["foo"; "bar"; "baz"];;
    - : string = "foobarbaz"
    

    You might also use the same Addable module signature with first class modules to avoid the need for the functors, allowing one function to sum lists of different types.

    # let sum_list 
          (type a) 
          (module A : Addable with type t = a) 
          (lst : a list) = 
        List.fold_left A.add A.nil lst;;
    val sum_list : (module Addable with type t = 'a) -> 'a list -> 'a = <fun>
    # module IntAdd = struct
        type t = int
        let nil = 0
        let add = (+)
      end;;
    module IntAdd : sig type t = int val nil : t val add : t -> t -> t end
    # module StringAdd = struct
        type t = string
        let nil = ""
        let add = (^)
      end;;
    module StringAdd : sig type t = string val nil : t val add : t -> t -> t end
    # module FloatAdd = struct
        type t = float
        let nil = 0. 
        let add = (+.)
      end;;
    module FloatAdd : sig type t = float val nil : t val add : t -> t -> t end
    # sum_list (module IntAdd) [1; 2; 3; 4; 5];;
    - : int = 15
    # sum_list (module StringAdd) ["1"; "2"; "3"; "4"; "5"];;
    - : string = "12345"
    # sum_list (module FloatAdd) [1.; 2.; 3.; 4.; 5.];;
    - : float = 15.