Why no compiler error for overlaying a parameter with a local val/var?

Why doesn’t the Scala compiler generate a compilation error for the code below?

object testval {
  def f(i:Int) = {
    val i = 2
    println("i=" + i)
  }

  def main(args: Array[String]) {
    f(1)
  }
}

This actually cost me day, just because I come from the C++ mindset of “if it compiles, it works.” So in refactoring I stuck in a parameter assuming that the compiler would remind me to delete the local val at a later date. Refactor and forget – the compiler will remind me. Except it didn’t.

Because there is no error. When you write val i = 2 Scala declares a new variable also called i but which has nothing to do with the parameter i. After that declaration, the parameter of f is shadowed and you cannot reach it anymore. I agree it can be quite confusing when you come from C++ but I think it is a common behaviour in more functionnal programming laguages.

If you want to trigger a compilation error, you can write i = 42 instead for instance. You will get

error: reassignment to val
1 Like

What is the value-add for it to not be a compiler error? How is it more functional?

My IDE (IntelliJ Idea) does not give a warning about this. It seems that it should: the new val has a whole lot to do with the function parameter. It has the same name.

Warning for the unused param is sufficient for this use case.

apm@mara:~$ scala -Xlint:help
Enable or disable specific warnings
  adapted-args               Warn if an argument list is modified to match the receiver.
  nullary-unit               Warn when nullary methods return Unit.
  inaccessible               Warn about inaccessible types in method signatures.
  nullary-override           Warn when non-nullary `def f()' overrides nullary `def f'.
  infer-any                  Warn when a type argument is inferred to be `Any`.
  missing-interpolator       A string literal appears to be missing an interpolator id.
  doc-detached               A Scaladoc comment appears to be detached from its element.
  private-shadow             A private field (or class parameter) shadows a superclass field.
  type-parameter-shadow      A local type parameter shadows a type already in scope.
  poly-implicit-overload     Parameterized overloaded implicit methods are not visible as view bounds.
  option-implicit            Option.apply used implicit view.
  delayedinit-select         Selecting member of DelayedInit.
  by-name-right-associative  By-name parameter of right associative operator.
  package-object-classes     Class or object defined in package object.
  unsound-match              Pattern match may not be typesafe.
  stars-align                Pattern sequence wildcard must align with sequence component.
  constant                   Evaluation of a constant arithmetic expression results in an error.
  unused                     Enable -Ywarn-unused:imports,privates,locals,implicits.
Default: All choices are enabled by default.

apm@mara:~$ scala -Ywarn-unused:help
Enable or disable specific `unused' warnings
  imports    Warn if an import selector is not referenced.
  patvars    Warn if a variable bound in a pattern is unused.
  privates   Warn if a private member is unused.
  locals     Warn if a local definition is unused.
  params     Warn if a value parameter is unused.
  implicits  Warn if an implicit parameter is unused.
Default: All choices are enabled by default.

apm@mara:~$ scala -Ywarn-unused
Welcome to Scala 2.12.2 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_111).
Type in expressions for evaluation. Or try :help.

scala> def f(i: Int) = { val i = 2 ; i + 1 }
<console>:11: warning: parameter value i in method f is never used
       def f(i: Int) = { val i = 2 ; i + 1 }
             ^
f: (i: Int)Int

You can get some shadowing help:

apm@mara:~$ scala -Xlint
Welcome to Scala 2.12.2 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_111).
Type in expressions for evaluation. Or try :help.

scala> class C(var c: Int) ; class D(c: Int) extends C(c) { def f = c }
<console>:11: warning: private[this] value c in class D shadows mutable c inherited from class C.  Changes to c will not be visible within class D - you may want to give them distinct names.
       class C(var c: Int) ; class D(c: Int) extends C(c) { def f = c }
                                                                    ^
defined class C
defined class D

Warning for shadowing in general is trickier to find a sweet spot for, especially with local functions (if you tend to reuse idents in benign ways).

It might be enough to say, if I shadowed or hid it in a way that isn’t benign, then probably it went unused.

Hello,

I’m not sure how that behavior is more functional, but if I remember
correctly, Scala’s scoping rules are the same as Java’s.

I don’t know the history behind it, but it seems plausible that they just
wanted to keep scoping rules simple and transparent: whatever is inside a
pair of curly braces shadows whatever is outside.

In C, on the other hand, there is no scope finer than method scope (C++
introduced block scopes). Or at least that was the status back then when I
used it, maybe it has changed since.

In very old dialects of C, you could not even declare the type in the
function header, so you had to declare it in the body.

Besides, in C/C++ is it possible to declare the same symbol multiple
times, but in Java/Scala, every symbol can only be declared once (modulo
overrides).

 Best, Oliver
1 Like

I was thinking about theses scoping rules for nested function definitions but Oliver gave a more general explanation, I did not realize it was that general.

Thanks for clarifying this and sorry for the confusion.

Java, like C++, gives a compiler error for naming a local variable the same as a parameter. Since there seems to be no functional programming rationale for the current situation, it seems to me it would be keeping in line with the “type safe” philosophy of Scala to detect this at compile time by default. Under the current scenario of not generating a compiler error by default, it gives Scala a more dynamic-typing feel. Making it a compiler error would ease the transition to Scala for C++ and Java programmers.

I agree with Michael. The previous answers miss the point: this is not an “unused” problem and it is not a “scoping” problem. We are in one scope and the reference is used. Note that the Java compiler flags this pattern as an error, though why we would be using Java or C++ or C as a a standard is a bit of a mystery to me.

Hello,

You’re right, Java does not allow a method argument to be shadowed by a
symbol at the scope of method body.

Ok, here is the difference: in C, C++ and Java, the syntax for method
definitions involves a pair of braces delineating the method body.

In Scala - and here is where the functional character comes in - the
method definition syntax involves no braces - the method body is whatever
expression comes after the equal sign. For example:

def twice(i: Int): Int = 2*i

If we do see braces in method definitions - as we often do - these are
not part of the method definition syntax, but part of the expression
syntax. Braces delineate a block, which is an expression whose value is the
value of the last expression inside (Statements are expressions, too. Only
declarations are not). Every block has its own scope, which is distinct
from the method scope.

 Best, Oliver
1 Like

@glennmurray I disagree that my answer misses the point. I’ll illustrate further.

Introducing a local val will result in either a forward reference or an unused parameter:

scala> def f(i: Int) = { println(i) ; val i = 42 ; println(i) }
<console>:11: error: forward reference extends over definition of value i
       def f(i: Int) = { println(i) ; val i = 42 ; println(i) }
                                 ^

If the declaration was intentional:

scala> def f(i: Int) = { println(i) ; locally { val i = 42 ; println(i) } }
f: (i: Int)Unit

One reason for not making it an error is the common idiom:

scala> def f(i: Int) = { def g(i: Int) = ??? ; g(i + 1) }
f: (i: Int)Int

Identifiers are commonly reused, not just x but also tree, etc.

Making that a built-in error would result in universal consternation.

However, it’s reasonable to enable a style rule in a linter. WartRemover et al might take a PR.

A java project at the office enables such a rule and it’s very annoying.

One idea for -Xlint was a flag to warn for unused param only if shadowed, that is, for this case. (Instead, it allows not warning for intentional unuse by checking for deprecation of the parameter.)

3 Likes