rescript-compiler icon indicating copy to clipboard operation
rescript-compiler copied to clipboard

Mutating a mutable optional field requires explicit optional value

Open glennsl opened this issue 2 years ago • 7 comments

Given this definition:

type t = {
  mutable foo?: int,
}

let x = {foo: 42}

I would expect both these ways of updating the field to work similarly:

let y = {...x, foo: 24}
let () = x.foo = 24 // Error: this has type: int, somewhere wanted: option<int>

Similarly with:

let y = {...x, foo: ?None}
let () = x.foo = ?None // Error: It seems that this record field mutation misses an expression

glennsl avatar Sep 07 '23 13:09 glennsl

The way to make the 2 mechanisms work similarly is to always force the use of x.foo = Some(24) which would make optional fields a pain to use. That inconsistency you notice is the price to pay to avoid having to do that.

Unless there's some other way that was not considered.

cristianoc avatar Sep 09 '23 03:09 cristianoc

The way to make the 2 mechanisms work similarly is to always force the use of x.foo = Some(24) which would make optional fields a pain to use.

Is that not how it currently works? x.foo = 24 does not compile, x.foo = Some(24) does.

Edit: rephrased OP to make it clearer what errors.

glennsl avatar Sep 09 '23 10:09 glennsl

The way to make the 2 mechanisms work similarly is to always force the use of x.foo = Some(24) which would make optional fields a pain to use.

Is that not how it currently works? x.foo = 24 does not compile, x.foo = Some(24) does.

Edit: rephrased OP to make it clearer what errors.

I should have said: uniformity would force {...x, foo: Some(24)}.

cristianoc avatar Sep 09 '23 18:09 cristianoc

That makes more sense. Still don't understand what's meaningfully different about them though? Both seem to have the same information available.

glennsl avatar Sep 09 '23 21:09 glennsl

I think I haven't really addressed your question. Your question in about inconsistency between mutable and immutable update.

  • Why not design mutable update to behave like immutable update? Had not considered this really. Let's see, one would change syntax and semantics of x.foo = .... Then I guess this assignment would stop working x.foo = y.foo but one would need to use x.foo = ?y.foo. Assuming that assignment pattern is a common one, the change would be quite invasive.

  • There's the possibility to make these distinctions disappear, paying some other cost for that privilege. If one were to allow passing values of type t where option<t> is expected, then I think all these special cases would go away. The price to pay: more magic that might be surprising to the developers, and some things to think about with polymorphic type inference where you just don't know whether you're injecting into option or not, yet.

cristianoc avatar Sep 10 '23 04:09 cristianoc

Thanks for the explanation.

  • Then I guess this assignment would stop working x.foo = y.foo but one would need to use x.foo = ?y.foo. Assuming that assignment pattern is a common one, the change would be quite invasive.

This is the same with immutable update though, {...x, foo: ?y.foo}. I would think that pattern is much more common. Is there something about mutation that makes it more invasive?

glennsl avatar Sep 10 '23 12:09 glennsl

Is there something about mutation that makes it more invasive?

Not sure really.

I guess the overarching feeling is that each choice has a significant weakness.

Something I'd like to see experimenting with would be to e.g. start small and do the following:

  • when type checking x.foo = e, if e is of manifestly non-option type then promote it to option type.

This means that all would work:

  1. x.foo = 3
  2. x.foo = Some(3)
  3. x.foo = y.foo

Then play with this solution for a while and assess whether there are issues working with this. If not, one could map the idea further to immutable mutation, pattern matching etc.

cristianoc avatar Sep 11 '23 05:09 cristianoc