Constant parameters in OCaml?

Hello,

I can’t find a way to tag function parameters as constant in OCaml. Something like const in C++.
If I call a function that takes an array as a parameter, I have no guarantee that it will or will not mutate my array? I just have to trust it?

OCaml is often praised for its error-proof type system so I find it weird that a function cannot indicate if it is read-only or read-write. It seems to make mutable data even more dangerous to handle than in mainstream OO languages? Is there a reason for it, and what are the common practices around it?

(edit: typo)

1 Like

One thing you might be able to try is giving your array a new abstract type, and only exposing the “immutable” parts of the Array module on that type:

module ImmutableArray : sig
  type 'a t
      
  val get: 'a t -> int -> 'a
  val of_array: 'a array -> 'a t
end = struct
  type 'a t = 'a array
  
  let get = Array.get
  let of_array = Fun.id
end

let () = 
  let arr = [| 1; 2; 3 |] in
  let imm_arr = ImmutableArray.of_array arr in
  Array.set imm_arr 0 (-1) (* doesn't compile *)
8 Likes

In OCaml, the read-only or write-only quality of a value is encoded as part of its type. Typically a read-only type would not expose any mutation operations and a write-only type would not expose any access operations.

In imperative languages, all data is mutable, and read-only, write-only annotations are used to restrict this mutability locally. In OCaml, almost all data is immutable, and only mutable record fields and arrays (+ its friends: byte arrays, flat float arrays, big arrays) can be mutated. The amount of mutable data in a typical OCaml program is thus much less than in your typical program written in an imperative language, so these kind of annotations are not as relevant.

As already mentioned in @zbaylin’s response, if you want a restrict the mutability of a type, you can do this typically by introducing a separate type that does not expose the operations that you want to restrict (eg writing or reading operations), but the simplest is not to use mutable data to begin with. If you must must use mutable data, you can either clearly specify your invariants (“trust it”), or introduce a suitable type to restrict the operations possible with it.

Incidentally, for the case of arrays, the addition of immutable arrays is being discussed upstream: Immutable arrays by OlivierNicole · Pull Request #13097 · ocaml/ocaml · GitHub.

Cheers,
Nicolas

9 Likes

As an addition to @zbaylin’s response, you can encode readability and writability in phantom types.

module Ref : sig
  type ('a,'b) t
  val create : 'a -> ('a,[`RW]) t
  val set : ('a,[<`WO|`RW]) t -> 'a -> unit
  val get : ('a,[<`RO|`RW]) t -> 'a
  val readonly : ('a,'b) t -> ('a,[`RO]) t
  val writeonly : ('a,'b) t -> ('a,[`WO]) t
end = struct
  type ('a,'b) t = 'a ref
  let create x = ref x
  let set t' x = t' := x
  let get t' = !t'
  let readonly x = x
  let writeonly x = x
end
let () =
  let v1 = Ref.create 20 in
  let v2 = Ref.readonly v1 in
  let v3 = Ref.writeonly v1 in
  Printf.printf "%d\n" (Ref.get v1) ;
  Printf.printf "%d\n" (Ref.get v2) ;
  (* won't compile *)
  (* Printf.printf "%d\n" (Ref.get v3) ; *)
5 Likes