Scala рекурсивное поведение val

Как вы думаете, распечатывает?

val foo: String = "foo" + foo
println(foo)

val foo2: Int = 3 + foo2
println(foo2)

Ответ:

foonull
3

Зачем? Есть ли часть в спецификации, которая описывает / объясняет это?

РЕДАКТИРОВАТЬ: чтобы прояснить мое удивление - я понимаю, что foo не определено в val foo: String = "foo" + foo и именно поэтому он имеет значение по умолчанию null (ноль для целых чисел). Но это не кажется "чистым", и я вижу мнения, которые согласны со мной. Я надеялся, что компилятор не позволит мне сделать что-то подобное. Это имеет смысл в некоторых частных случаях, например, при определении Streams, которые по своей природе ленивы, но для строк и целых чисел я бы ожидал, что либо остановлю меня из-за переназначения val, либо скажу, что я пытаюсь использовать неопределенное значение, как будто я написал val foo = whatever (При условии whatever никогда не был определен).

Чтобы еще больше усложнить ситуацию, @dk14 указывает, что это поведение присутствует только для значений, представленных в виде полей, и не происходит внутри блоков, например

val bar: String = {
  val foo: String = "foo" + foo // error: forward reference extends...
  "bar"
}

4 ответа

Решение

Да, см. Раздел SLS 4.2.

foo это String который является ссылочным типом. Значение по умолчанию: null, foo2 является Int который имеет значение по умолчанию 0.

В обоих случаях вы имеете в виду значение val, которое не было инициализировано, поэтому используется значение по умолчанию.

В Scala Int поддерживается примитивным типом (int), а его значение по умолчанию (до инициализации) равно 0.

С другой стороны, String является Object который действительно null когда не инициализирован.

В обоих случаях foo соответственно foo2 имеют значения по умолчанию в соответствии со спецификацией JVM. Это null для ссылочного типа и 0 для int или же Int - как говорит это Скала.

К сожалению, scala не так безопасен, как, скажем, Haskell, из-за компромиссов с ООП-моделью Java ("Объектно-ориентированное и функциональное соответствие"). Существует безопасное подмножество Scala под названием Scalazzi, и некоторые из плагинов sbt / scalac могут дать вам больше предупреждений / ошибок:

https://github.com/puffnfresh/wartremover ( не проверяет найденный вами случай)

Возвращаясь к вашему случаю, это происходит только для значений, представленных в виде полей, и не происходит, когда вы находитесь внутри функции / метода / блока:

scala> val foo2: Int = 3 + foo2 
foo2: Int = 3

scala> {val foo2: Int = 3 + foo2 }
<console>:14: error: forward reference extends over definition of value foo2
       {val foo2: Int = 3 + foo2 }
                            ^

scala> def method = {val a: Int = 3 + a}
<console>:12: error: forward reference extends over definition of value a
       def method = {val a: Int = 3 + a}

Причиной такой ситуации является интеграция с Java как val компилируется в final поле в JVM, поэтому Scala сохраняет все особенности инициализации классов JVM (я полагаю, что это часть JSR-133: модель памяти Java). Вот более сложный пример, который объясняет это поведение:

 scala> object Z{ val a = b; val b = 5}
 <console>:12: warning: Reference to uninitialized value b
   object Z{ val a = b; val b = 5}
                     ^
 defined object Z

 scala> Z.a
 res12: Int = 0

Итак, здесь вы можете увидеть предупреждение, которое вы не видели в первую очередь.

Другие вопросы по тегам