reasonml / reason

Simple, fast & type safe code that leverages the JavaScript & OCaml ecosystems
http://reasonml.github.io
MIT License
10.12k stars 428 forks source link

v2 syntax still used in `lazy` pattern matching #2575

Closed TheSpyder closed 1 month ago

TheSpyder commented 4 years ago

I have come across a case where refmt not only supports v2 syntax but outputs it 😮

While reading the BuckleScript blog post about the new lazy encoding, I noticed a cool way to unpack lazy values without calling Lazy.force:

let (lazy(value)) = myLazyValue;

Or rather, that's what I expected the syntax to be. Turns out the syntax in the blog post is not only valid reason, it's the syntax refmt converts the above to. No brackets.

let lazy value = myLazyValue;

proof this syntax works: https://reasonml.github.io/en/try?rrjsx=true&reason=DYUwLgBAtgngMgQwF4wGoOAVxBAvBYZGACgCIALAS1IEoBuAKAYHoAqCAd3IUgEkIQADwAOIAMaRWzBqEjFCKYgDcM2GjTzR4RdFhCMAUgGcAdMAD2Ac2WqQ9Jm07dIAJxAAzKJHOYww30YQUjLgBEQQKnqasIgoutiGphbWkWqMQA

I'm using

$ ./node_modules/.bin/bsrefmt --version
Reason 3.6.0 @ 8f71db0
TheSpyder commented 4 years ago

Counter argument: perhaps defining a lazy should be a changed to bracket-less syntax to match this pattern. As discussed in discord last night, it's confusing: https://discord.com/channels/235176658175262720/235199119747055616/711938366194843788

Rather than lazy(myFunction(something)) it would be nice to have lazy myFunction(something). I think this would solve the ambiguity compared to otherFunction(myFunction(something)); the brackets here for lazy() make it less friendly imo, not more.

yawaramin commented 4 years ago

Parentheses were added for constructing lazy values in #2376. However I guess we missed adding them to the lazy(...) pattern as well at the time (I didn't know it could be a pattern at the time). I think for overall consistency it's better to add it to the pattern e.g. let lazy(x) = lazy(1);.

TheSpyder commented 4 years ago

I didn't know about it either until the blog post 😂

I agree we should be consistent, but after the discord discussion I think I'm leaning towards reverting to never have brackets rather than adding them to the pattern matching.

yawaramin commented 4 years ago

We should consider overall syntax consistency. Reason syntax makes any kind of value construction look like function application e.g. let x = Some(0);. And conversely so does destructuring e.g. let Some(y) = x;. This simplifies the syntax overall–you don't need to remember a special rule for constructing and destructuring lazy values.

TheSpyder commented 4 years ago

I think special syntax for special things makes sense, for example in JS await doesn't use function syntax. If reasonml ever adds that directly (instead of via let.await) it will definitely be expected to not use function syntax.

For constructing values, sure, function syntax makes sense. But lazy doesn't construct a value, it suspends constructing a value.

yawaramin commented 4 years ago

Reason has already added let-operators. It's part of the language now. [Edit: I think I misunderstood you, do you mean if Reason adds await syntax after already adding let-operators? That's possible I suppose but I don't think it's worth considering too much.]

lazy constructs a value of type Lazy.t.

TheSpyder commented 4 years ago

Yes, I meant adding await even though we have let-operators. let.await isn't the same thing; it requires a library to implement it.

Lazy.t is only a value in the strictest sense of the word. As far as developers using it are concerned, it's about as much of a value as Promise.t is. lazy(1+2) doesn't produce 3 until you force the computation.

yawaramin commented 4 years ago

But the whole point of promises is to turn asynchronous computations into first-class values that can be passed around. Lazy values are values in exactly the same sense. Anyway I think we are really in #bikeshedding territory now.

TheSpyder commented 4 years ago

We are definitely bikeshedding, but if I do nothing brackets will be added. I'm growing more convinced reverting brackets is the better answer.

But the whole point of promises is to turn asynchronous computations into first-class values that can be passed around. Lazy values are values in exactly the same sense.

Yes, they are both special values that suspend computation from the line of code they're defined in, which is why having special syntax for both seems fine to me. let.await is a form of special syntax, so is await if that later becomes important enough to include, but I can't define let.lazy.

yawaramin commented 4 years ago

await is not constructing a promise, it's binding to an existing promise and letting a continuation run on it. It's not the same as lazy(...). In fact JavaScript promises don't even suspend computation, they run eagerly. So the semantics of await really are quite different. In the hypothetical low probability of await being added to Reason, it's perfectly sensible for it to look like JS await.

TheSpyder commented 4 years ago

Promises suspend computation at the line of code they are defined on, yes they run after the current stack frame but that's an implementation detail.

I don't actually use async/await so I always get them confused. I guess async is the one I meant then? My point was that JS programmers are already used to special syntax that changes how the line of code operates, whether that's binding to the promise result or creating the promise to begin with. We should be fine with it too.

yawaramin commented 4 years ago

We've spent quite a bit of time talking about a hypothetical async/await syntax for Reason, something I'm fairly sure will not happen now that let-operators are officially part of the language. But just to be clear. There's no special syntax in JS for creating promises, unless you count marking a function as async which lifts its return value into a promise. But this is syntactically separate from the return statement itself.

As I mentioned before, we already have a syntax for constructing values in Reason, and it looks like function application. Yes, constructing lazy values is semantically different, but it's not different enough that it warrants some special rule. Consider that in OCaml syntax, it looks like normal function application too: let x = lazy 1. I don't see any justification for Reason to be different.