-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Identifier shadowing in for-comprehensions is only supported in flatMap
s
#4525
Comments
I understand your reasoning, and (some?) MLs support this well, but the error suggest that If we could (and we probably can't), I suspect forbidding OTOH, if I test
|
But is the recursive definition of a local |
@joroKr21 scala> val f: List[Int] => List[Int] = { case Nil => Nil; case x :: xs => x + 1 :: f(xs) }
val f: List[Int] => List[Int] = Lambda$1715/318787032@4fc6e776
scala> f(List(1, 2, 3))
val res2: List[Int] = List(2, 3, 4) and we can't very well change the scoping of For more compelling examples of this, I'd maybe look at function types hidden behind aliases, e.g. in parser combinators, where IIRC there's better reasons to not write EDIT: had posted before being done. |
Emphasis on local: scala> val cd: Int => String = t => if (t <= 0) "lift-off" else cd(t - 1)
val cd: Int => String = Lambda$1492/2086673744@79b2852b
scala> def foo = { val cd: Int => String = t => if (t <= 0) "lift-off" else cd(t - 1); cd }
1 |def foo = { val cd: Int => String = t => if (t <= 0) "lift-off" else cd(t - 1); cd }
| ^^
| `cd` is a forward reference extending over the definition of `cd`
But I agree it might be too inconsistent for a Edit: on the other hand, the scoping rules in for-comprehensions are also inconsistent unless you know the desugaring 😃 |
Oh, I stand corrected. And it's clearer why object fields need be allowed to refer to object fields than why that would work for locals. Still, not sure how to keep things consistent overall — or how to not make them less consistent.
It seems Scala's answer is "know the desugaring" right now, tho if somebody wants to push to get I'd also first try to resolve #2573 — some of the options there would actually desugar But overall, I don't have a strong preference either way. |
I just wanted to add that Scala lacking shadowing of local variable is a huge usability problem IMHO. In functional languages like OCaml, where values are usually immutable, you can do: let name = getName() in
let name = name |> capitalize in
print_string name In Scala, you have to come up with silly names that pollute the namespace: val name = getName()
val nameCap = name.capitalize
println(nameCap) ...and in my experience, this is actually a huge source of mistakes. It is all too easy to write Now, it's probably too late to change the scoping rules of Scala, but maybe something can be done to alleviate this usability hazard in a backward-compatible way. val name = getName()
new val name = name.capitalize
println(name) Then we can then say that for-comprehension bindings have the semantics of |
I also don't like |
It's true that this can help, but it does not work in general, as the type of a transformed value can change after a transformation (especially, it can be refined). For example, we may have a newtype |
But if the type changes, probably you shouldn't be using the same variable name... |
@Sciss the argument is that inventing new names is hard, so at some point you're going to use dummy names like Anyway, at this point I guess it depends on individual preferences. Though I think it's valuable to remember that ML programmers have been doing this for a long time without problems (AFAIK). |
@LPTK I've thought about this more, and I'm very in favor. It is somewhat remniscent of linear types, in terms of ability to model tiny pipelines without the risk of making mistakes of using the wrong intermediate value. I think people would quickly realize how handy this could be for streamlining certain idioms. Two follow up ideas:
|
@LPTK I only just saw your proposal, and I have no idea if it would be a net benefit or not, but full marks for audacity! |
@acjay interesting, so you would have guards such as this? new val name = name.capitalize if name != "BOB" // 'name' in guard refers to new value? I'm not sure I see the value with that though, as it's easy to write with an new val name = name.capitalize optionIf (_ != "BOB") getOrElse name |
@LPTK, yeah, you could use a full if-else, but I think it would enable a more direct modeling of business logic when you want fall-through behavior. These guards are a common idiom in Ruby, except there, there are nasty gotchas with mutability and nullability. Restricting usage of guards to For me this feature is particularly useful when folding over a data structure, propagating a bookkeeping case class asking along the way, using |
There's actually a case where you can't use var even when all the values are of the same type. // What I want to do // What I have to do in Scala |
this has long seemed like a bit of inadequacy to me |
I think the shadowing is acceptable within the for-comprehension only because its scope is quite limited. Shadowing of vals has much more potential to be accidental, so I would prefer this not to be possible in general. Regarding the possibility of changing the type during shadowing, this would be very much desirable. As a simple example of how this could be useful, we could be constructing an |
@propensive what if you were only allowed to shadow with a value that shares a non-trivial super type? The specific type of the new value would be retained though. In any case, it seems like warning settings could give people some control over the strictness they'd like. Or really whether they'd want to allow |
@acjay I think it's worthy of consideration, though I don't like that it would require the introduction of another concept ("nontrivial supertype") even though I have a pretty good notion of what that would be. That same concept, if it existed, could be useful and even desirable elsewhere (e.g. if a LUB were inferred to be a nontrivial supertype of the parameters to a Anyway, a much simpler argument is that I don't think it would be useful. If we are trying to avoid accidental shadowing of unrelated values, the typechecker would discover all the problematic cases anyway, because "nontrivial supertypes" are exactly the ones without useful interfaces. There might also be performance considerations. I don't believe the compiler currently is aware when shadowing happens, other than to put the symbols into a data structure which makes the shadowed identifiers inaccessible. It might be expensive (even 2% overhead would undo a lot of good work that's already gone into improving compiler performance) to do these checks. |
In dotc 0.7.0, this compiles,
but this does not,
It gives the errors in the REPL,
It's probably an accident of implementation, but it's very useful for getting safe mutable-like syntax in long chained for-comprehensions, without needing to resort to introducing many different identifier names, provided every generator in the for-comprehension is a
flatMap
, not amap
.This is a quirk of Scala 2, and it would be nice to remove it in Scala 3.
The text was updated successfully, but these errors were encountered: