Mutable variable

Hi,

I wish ReScript had ‘mutable’ variable apart from ref cell and overriding let binding
Something like

let mut state = 10
state = 11

I am writing a piece of code like this today…

let state = ref(initialState)
for i in len-2 downto 0 {
    state := folder(arr[i], state.contents)
    result[i] = state.contents
}

and wish to write like below

let mut state = initialState
for i in len-2 downto 0 {
    state = folder(arr[i], state)
    result[i] = state
}

Sorry for my English. Not a native speaker.

Thanks & Regards
Jazz

What do you gain though? How is your second snippet better than the first one?

1 Like

A record with mutable fields is more ergonomic if you have multiple values to hold.

type state = {
  mutable count: int
}

let state = {count: 0}
state.count = state.count + 1

In your code example however, it is possible to use reduce instead of variable mutation:

module A = Js.Array2

let data = [1, 2, 3]
let initialState = 0

let result = []
data->Belt.Array.reverse->A.sliceFrom(2)->A.reduce((acc, elem) => {
  let acc = acc->folder(elem)
  result->A.push(acc)->ignore
  acc
}, initialState)->ignore

result

Note that we’re doing side-effects (array mutation) inside reduce.

2 Likes

The question’s about readability, and I sympathize with wanting better sugar than state.contents. Though unfortunately that’s a very invasive compiler change that’s not considered soon.

Since we’re talking about readability, I’m also not sure the reduce version is better. Tbh aside from the canned reduce examples in FP tutorials, I haven’t found it to be more readable than just a loop or recursion in any codebase.

Speaking of which, the recursive version avoids both mutability (apart from the array assignment) and reduce:

let rec loop = (state, i) => {
  if i >= 0 {
    let newState = folder(arr[i], state)
    result[i] = newState
    loop(newState, i - 1)
  }
}

loop(initialState, len - 2)
4 Likes

To give some insight into why mutable variables aren’t possible, consider this trivial example:

let setToOne = x => x := 1

let f = () => {
  let x = ref(0)
  setToOne(x)
  x
}

If x is compiled to a plain JS number value, setToOne wouldn’t do anything. The fact that refs are records with a mutable field means that we can safely pass them around.

2 Likes

I think I would write it as @chenglou does. Something along the lines of

let process' = (f, initialState, arr, result) => {
  let rec loop = (state, i) => {
    if i >= 0 {
      let newState = f(arr[i], state)
      result[i] = newState
      loop(newState, i - 1)
    }
  }

  loop(initialState, Array.length(arr) - 2)
}

(The same solution, just local-binding the loop function so it is local and doesn’t pollute the scope more than is necessary).

I can definitely see why you might want something like let mut x =... since that’s somewhat what Rust does. Though one important thing is that mut is a modifier in Rust, so you can have stuff such as let (a, mut b) = ... making b mutable, but not a. The OCaml root, which to a certain extent informed the type system of Rescript, puts the designator on the value, and not on the variable. I have a hunch this is why you have the current situation and why it isn’t that easy to make the change in the first place.

There is also a question of what you want to encourage in programming solutions. If you provide affordance toward easy handling of ref-cells, you will make that more prevalent in programs. If you provide pressure against ref-cells, but still allow them, you encourage people to use a more functional style in their programming. However, because you still have them, you can employ mutation when it is required (for efficiency reasons of the resulting program), or if the algorithm / data structure is easier to describe with mutation.

As for reduce, I tend to shy away from them in the case where you aren’t processing the full array. You need to use extra code to “select” the right array subset, and unless you have an efficient way of doing so, built into your usual language vocabulary, a more direct approach tend to be easier to read. Also, we are more doing something for its side-effect, namely updating result, and a reducer wants to end up with a final value we can output. I.e., we can write this in terms of reduce, but it’s different enough that I would rather see a recursion loop, because that would make me think about what is happening.

5 Likes

Thanks, this is a good discussion. A lot of us are (re)discovering the language idioms, and more of this kind of conversations can help fill in the gaps.

4 Likes

Hi @alexeygolev ,

I think it would make my second snippet more readable.

Regards,
Jazz

@chenglou
Thank you for the info.
And the loop version is cool. I would update my code.

@jlouis
Cool!. Thank you.

Why is the sugar available in reason and not rescript? Have they diverged that much?