Вопрос по initialization, scala – Определение значений экземпляра Scala

3

Note that this question and similar ones have been asked before, such as in Прямые ссылки - почему этот код компилируется?, но я нашел ответы, чтобы все еще оставить некоторые вопросы открытыми, поэтому у меня есть другой подход к этому вопросу.

В рамках методов и функций, эффект отval Ключевое слово выглядит как лексическое, т.е.

<code>def foo {
  println(bar)
  val bar = 42
}
</code>

получая

<code>error: forward reference extends over definition of value bar
</code>

Тем не менее, в классах, обзорные правилаval кажется, измениться:

<code>object Foo {
  def foo = bar
  println(bar)
  val bar = 42
}
</code>

Это не только компиляция, но иprintln в конструкторе будет давать0 как его вывод, при вызовеfoo после того, как экземпляр полностью построен, получится ожидаемое значение42.

Таким образом, представляется возможным для методов пересылать ссылки на значения экземпляров, которые, в конечном итоге, будут инициализированы до вызова метода (если, конечно, вы не вызываете его из конструктора), а также для операторов внутри конструктора направлять ссылочные значения таким же образом, получая доступ к ним до их инициализации, что приводит к глупому произвольному значению.

Отсюда возникает пара вопросов:

Why does val use its lexical compile-time effect within constructors?

Учитывая, что конструктор - это на самом деле всего лишь метод, это кажется довольно непоследовательным, чтобы полностью отброситьval'Эффект времени компиляции, придающий ему только обычный эффект времени выполнения.

Why does val, effectively, lose its effect of declaring an immutable value?

Доступ к значению в разное время может привести к разным результатам. Для меня это очень похоже на утечку деталей реализации компилятора.

What might legitimate usecases for this look like?

Мне трудно придумать пример, который абсолютно требует текущей семантикиval внутри конструкторов и не могут быть легко реализованы с помощью правильной, лексическойvalвозможно в сочетании сlazy.

How would one work around this behaviour of val, getting back all the guarantees one is used to from using it within other methods?

Можно, по-видимому, объявить все инстанцииvalс бытьlazy чтобы вернуться кval быть неизменным и давать один и тот же результат, независимо от того, как к ним осуществляется доступ, и сделать эффект времени компиляции, наблюдаемый в обычных методах, менее актуальным, но это кажется мне ужасным хаком для такого рода вещей.

Учитывая, что это поведение вряд ли когда-либо изменится в реальном языке, будет ли плагин компилятора подходящим местом для решения этой проблемы, или возможно ли реализоватьval-подобное ключевое слово с, для кого-то, кто только что потратил час на отладку проблемы, вызванной этой странностью, более разумной семантики в языке?

Ваш Ответ

2   ответа
3

Given that a constructor is really just a method ...

Это не так.

It doesn't return a result and doesn't declare a return type (or doesn't have a name) It can't be called again for an object of said class like "foo".new ("bar") You can't hide it from an derived class You have to call them with 'new' Their name is fixed by the name of the class

Ctors немного похожи на методы из синтаксиса, они принимают параметры и имеют тело, но это все.

Why does val, effectively, lose its effect of declaring an immutable value?

Это не так. Вы должны взять элементарный тип, который не может быть нулевым, чтобы получить эту иллюзию - с объектами это выглядит иначе:

object Foo {
  def foo = bar
  println (bar.mkString)
  val bar = List(42)
}
// Exiting paste mode, now interpreting.

defined module Foo

scala> val foo=Foo 
java.lang.NullPointerException

Вы не можете изменить значение 2 раза, вы не можете присвоить ему значение, отличное от нуля или 0, вы не можете изменить его обратно, и другое значение возможно только для элементарных типов. Так что это далеко от того, чтобы быть переменной - это - может быть - неинициализированное - окончательное значение.

What might legitimate usecases for this look like?

Я предполагаю работать в REPL с интерактивной обратной связью. Вы выполняете код без явного объекта или класса переноса. Чтобы получить эту мгновенную обратную связь, ее нельзя ждать, пока (неявный) объект не закроется}, Следовательно, класс / объект не читается в двухпроходном режиме, когда сначала выполняются все объявления и инициализации.

How would one work around this behaviour of val, getting back all the guarantees one is used to from using it within other methods?

Не читайте атрибуты в Ctor, как вы не читаете атрибуты в Java, которые могут быть перезаписаны в подклассах.

update

Подобные проблемы могут возникнуть в Java. Компилятор предотвращает прямой доступ к неинициализированному конечному атрибуту, но если вы вызываете его через другой метод:

public class FinalCheck
{
    final int foo;

    public FinalCheck ()
    {
        // does not compile: 
        // variable foo might not have been initialized
        // System.out.println (foo);

        // Does compile - 
        bar ();

        foo = 42;       
        System.out.println (foo);
    }

    public void bar () {
        System.out.println (foo);   
    }
    public static void main (String args[])
    {
        new FinalCheck ();
    }
}

... вы видите два значения дляfoo.

0
42

Я не хочу оправдывать это поведение, и я согласен, что было бы неплохо, если бы компилятор мог предупреждать соответственно - в Java и Scala.

Возможность изменить значение только один раз и не более того, что не меняет того факта, что оно действительно изменяется и, следовательно, не является должным образом неизменным, не так ли? Точно так же изменение от 0 до 42, по-видимому, совсем не похоже на изменение от нуля до экземпляра объекта. rafl
Отсутствие какой-либо работы, полностью зависящей от атрибутов в конструкторах, - это, безусловно, способ избавиться от недостатков того, какval ведет себя в конструкторах. Однако при этом я бы вводил больше состояний, что вряд ли является благоприятным выбором. rafl
Мне было бы интересно прочитать о том, что конструкторы не являются методами. rafl
@rafl: a) В последнее время я подробно остановился на вопросе ctor, но перед вашим комментарием. Вы читали это? б) оно не изменено - оно инициализировано. Только для Int и таких это похоже на изменение. Объявите и инициализируйте перед использованием, если вы используете их из ctor. b2) Использование 0 непреднамеренно может остаться незамеченным, в то время как исключение NullPointerException будет замечено - в этом разница. c) В вашем примере неясно, почему вы не используете «println (42)». Если это не окончательное значение, возможно, класс лучше, чем объект, где вы инициализируете планку до 42 в ctor?
б) можно наблюдать его изменение от одного состояния к другому, например от0 в42или изnull List пример. Хорошо, если вы предпочитаете называть это инициализацией, а не мутацией, но тот факт, что значение атрибута, которое, согласно его объявлению, никогда не изменится, изменится, останется, тогда как это не произойдет, еслиval внутри тела конструктора ведут себя так же, как и везде, т. е. делают видимость символаlexically область видимости. rafl
2

values, which will, eventually, be initialised before the method can be called (unless, of course, you're calling it from the constructor), and for statements within the constructor to forward-reference values in the same way, accessing them before they've been initialised, resulting in a silly arbitrary value.

Конструкторis конструктор. Вы строите объект. Все его поля инициализируются JVM (в основном, обнуляются), а затем конструктор заполняет все поля, которые необходимо заполнить.

Why does val use its lexical compile-time effect within constructors?

Given that a constructor is really just a method, this seems rather inconsistent to entirely drop val's compile-time effect, giving it its usual run-time effect only.

Я понятия не имею, что вы говорите или спрашиваете здесь, но конструктор не метод.

Why does val, effectively, lose its effect of declaring an immutable value?

Accessing the value at different times may result in different results. To me, it very much seems like a compiler implementation detail leaking out.

Это не так. Если вы попытаетесь изменитьbar из конструктора вы увидите, что это невозможно. Доступ к значению в разное времяin the constructor может привести к различным результатам,of course.

Выconstructing объект: он начинается не построенным, а заканчивается построенным. Чтобы это не изменилось, оно должно начинаться с его окончательного значения, но как оно может это сделать, если кто-то не присвоит это значение?

Угадай, кто это делает? Конструктор

What might legitimate usecases for this look like?

I'm having a hard time coming up with an example that absolutely requires the current semantics of val within constructors and wouldn't easily be implementable with a proper, lexical val, possibly in combination with lazy.

Нет никакого варианта использования для доступа к val до того, как его значение было заполнено. Просто невозможно выяснить, было ли оно инициализировано или нет. Например:

class Foo {
  println(bar)
  val bar = 10
}

Как вы думаете, компилятор может гарантировать, что он имеетnot были инициализированы? Хорошо, тогда откройте REPL, поместите в вышеупомянутый класс, и тогда это:

class Bar extends { override val bar = 42 } with Foo
new Bar

И посмотри чтоbar был инициализирован при печати.

How would one work around this behaviour of val, getting back all the guarantees one is used to from using it within other methods?

Объявите ваши vals перед их использованием. Но обратите внимание, что конструкторnot метод. Когда вы делаете:

println(bar)

внутри конструктора вы пишете:

println(this.bar)

А такжеthisобъект класса, для которого вы пишете конструктор, имеетbar добытчик, так называется.

Когда вы делаете то же самое на методе, гдеbar есть определение, нетthis сbar добытчик.

Похожие вопросы