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
(ноль для целых чисел). Но это не кажется "чистым", и я вижу мнения, которые согласны со мной. Я надеялся, что компилятор не позволит мне сделать что-то подобное. Это имеет смысл в некоторых частных случаях, например, при определении Stream
s, которые по своей природе ленивы, но для строк и целых чисел я бы ожидал, что либо остановлю меня из-за переназначения 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
Итак, здесь вы можете увидеть предупреждение, которое вы не видели в первую очередь.