In light syntax, when you do not use in with your let binding, it is implied and inserted by the precompiler (at least, that's how it is explained in the F# spec 15.1.1 and 15.1.2).
This works fine, you can use the in or not, it doesn't seem to matter in local bindings.
However, when you use it in a module-level let binding, it hides everything that follows.
Repro steps
```f#
module X =
let x = 42 in ignore x
let y = "test"
()
let x = X.y // error: 'y' is not defined
```
Expected behavior
After the in clause + expr, the declaration should end.
Actual behavior
Anything after the in clause is hidden. You can suddenly have multiple module-level let-bindings with the same name, though they won't be accessible.
Known workarounds
Do not use in to create a shortcut-expression at module-level.
There's no syntax that allows ending the in (at least none that I know of), though it should be ;;, but this isn't valid syntax. Keywords like end or done are not allowed here.
Related information
This appears to have been the case for as long as I could look back, possibly since F# 1.0, so by that one could argue that this is "by design". Though I couldn't find this defined anywhere, and on Slack there seemed to be similar surprise: (thread starts here) https://fsharp.slack.com/archives/C1R50TKEU/p1596912832495400
New insight/workaround, if you place the stuff after in on its own line, the problem goes away:
```f#
module X =
let x = 42 in
ignore x
let y = "test"
let x = X.y // no error anymore
```
(though still not sure why the scoping goes the way it does in the original code, though I think it has something to do with offside rules?)
still not sure why the scoping goes the way it does in the original code
Yes I think that in in conjunction with the default #light syntax has improper scoping. The following code compiles, when I would expect ignore to create an offside position, making the final x out of scope:
let y =
let x = 42 in
ignore x
x // This compiles but shouldn't
While:
let y =
let x = 42 in
ignore x
x // This doesn't compile but should
Do not use in to create a shortcut-expression at module-level.
I'm not sure whether the best thing is to fix this scoping and the module issues, or deprecate usage of in in light mode. It doesn't add a lot of value to F#, and the scoping issues here suggest to me that I should remove the couple of occasions I use in to make one-liners.
Interestingly, the following OCaml code compiles, because it has insignificant whitespace.
let y =
let x = 42 in ignore x;
x;;
But that doesn't mean the F# code should compile. The F# code should be equivalent to:
let y =
let x = 42 in ignore x; ;
x;; (* Error: Unbound value x *)
OCaml doesn't have the module issues described here, unsurprisingly as in gets a lot more testing in OCaml. E.g. the following code does not compile, while the original code with let y = "Test" does compile:
module X = struct
let x = 42 in ignore x
let y = x (* Error: Unbound value x *)
end
let z = X.y
With apologies for errors, first time using OCaml. Aside: the fact that verbosity allows spacing to be completely constrained is an interesting advantage.
I agree this wart should be ironed out.
It looks similar to #7741.
Most helpful comment
I agree this wart should be ironed out.