I am learning OCaml coming from F#. In F#, to calculate the average of an array whose element type supports addition and division, one can write
let inline average (arr: 'a[]) : 'a
when ^a : (static member DivideByInt : ^a * int -> ^a)
and ^a : (static member (+) : ^a * ^a -> ^a)
and ^a : (static member Zero : ^a)
=
if Array.length arr = 0 then (LanguagePrimitives.GenericZero) else
LanguagePrimitives.DivideByInt (Array.fold (+) (LanguagePrimitives.GenericZero) arr) (Array.length arr)
My understanding is that in OCaml, one would have a module type like so:
module type Averagable = sig
type 'a t
val divide_by_int : 'a -> int -> 'a
val plus : 'a -> 'a -> 'a
val zero : 'a
end
My question is how the corresponding function would be written:
While @smolkaj’s answer is a correct and direct implementation of your F# code, it might be nicer if your code can interplay with existing abstractions in the OCaml infrastructure. For example,
open Base
let average (type a) (module T : Floatable.S with type t = a) xs =
Array.fold ~init:0. ~f:(fun s x -> s +. T.to_float x) xs /.
Float.of_int (Array.length xs)
and now it could be used with any existing numeric data in Base/Core
average (module Int) [|1;2;3;4|];;
- : Base.Float.t = 2.5
and even adapted to non-numbers,
let average_length = average (module struct
include String
let to_float x = Float.of_int (String.length x)
let of_float _ = assert false
end)
The latter example shows that we requested more interface than need, a cost that we have to pay for using an existing definition. In cases when it matters, you can specify the specific interface, e.g.,
module type Floatable = sig
type t
val to_float : t -> float
end
let average (type a) (module T : Floatable with type t = a) xs =
Array.fold ~init:0. ~f:(fun s x -> s +. T.to_float x) xs /.
Float.of_int (Array.length xs)
But we reached the point where using first class modules is totally unnecessary. Our interface has only one function, so the following definition of average, is much more natural
let average xs ~f =
Array.fold ~init:0. ~f:(fun s x -> s +. f x) xs /.
Float.of_int (Array.length xs)
it has type 'a array -> f:('a -> float) -> float and computes an average of f x_i for all elements in the array.