Preventing mutations of mutable objects

Is there a way to mark a mutable type like an Js.Array2 as constant per the example below? Apologies if it is obvious, but I could not find any information on this.

let i_promise_to_not_modify = v => {
  let _ = v->Js.Array2.push(1)
}

let run = () => {
  let v = []

  let _ = v->Js.Array2.push(1)

  i_promise_to_not_modify(v)

  Js.log(v)
}

run()

This of course results in [1, 1]

Arrays are always mutable, but you can define a type alias for an array and use a module interface to control how other modules are allowed to use it.

module M: {
  type t
  let make: int => t
} = {
  type t = array<int>
  let make = i => [i]
}

let a = M.make(1)
Js.Array2.push(a, 2) // ERROR!

Inside module M, the compiler can see that type t is just an array, so you can use any array functions on it. Outside of module M, the compiler only knows what the module’s interface exposes. The fact that t is an array is hidden, so, unless you expose a function like push, then it will be impossible for outside code to mutate the array.

7 Likes

Can you do this with varargs int in ReScript too? :smiling_face_with_three_hearts:

I do not know the answer to cmwelsh’s question, but thank you to johnj for answering mine.

Can you explain what you mean? Variable arguments are generally not possible in ReScript, but either way I don’t know how they relate to this topic.

I see. I can either define the make function as taking an array of ints and assume the caller did not leak a reference to the array (would this be zero cost?), or I can defensively copy the array when creating the typed version of the variable.

1 Like

I see what you mean now. Yes, that sounds about right.

You could make it zero-cost by using external, but not if you want to defensively copy the array.

module M: {
  type t
  let make: array<int> => t
  external make_zero_cost: array<int> => t = "%identity"
  let make_copy: array<int> => t
} = {
  type t = array<int>
  let make = a => a
  external make_zero_cost: array<int> => t = "%identity"
  let make_copy = a => Js.Array2.copy(a)
}

As long as you expose the external in the interface, then it will compile away in the JS output.

Is it currently possible somehow in the language, no matter how technical (macro, ppx, etc.), to determine automatically if the call site contains a static expression (i.e. array literal) and pick the compiler output based on that determination?

Thank you for the response, johnj.