Nano Hash - криптовалюты, майнинг, программирование

Странное поведение Scala при инициализации класса/объекта

Возможный дубликат:
Scala и прямые ссылки

Есть ли какое-то объяснение, почему в Scala работает следующее:

Версия 1

object Strange extends App {
  val x = 42
  Console.println(x) // => outputs "42", as expected
}

Версия 2

object Strange extends App {
  Console.println(x) // => "0" ?!
  val x = 42
}

Почему он вообще компилируется и почему ведет себя так странно без каких-либо предупреждений или чего-то подобного?

Та же проблема с class:

class StrangeClass {
  Console.println(x) // => still "0"
  val x = 42
}

object TestApp extends App {
  new StrangeClass()
}

С телом обычного метода такой проблемы нет:

def nonStrangeMethod {
  Console.println(y) // => fails with "not found: value y", as expected
  y = 42
}

И поведение резко изменится, если мы добавим «final» к объявлению val:

class StrangeClass {
  Console.println(x) // => "42", but at least that's expected
  final val x = 42
}

Для записей следующий статический аналог Java (Scala object):

public class Strange {
    static {
        System.out.println(x);
    }
    static int x = 42;

    public static void main(String[] args) {}
}

не удается выполнить компиляцию с простой и понятной ошибкой «Невозможно ссылаться на поле до его определения» в строке № 3 и нестатическом аналоге Java (Scala class):

public class Strange {
    Strange() {
        System.out.println(x);
        int x = 42;
    }

    public static void main(String[] args) {
        new Strange();
    }
}

явно терпит неудачу с "x не может быть преобразован в переменную" в строке № 3.


Ответы:


1

Это из-за признака App, который использует отложенную инициализацию. Из раздела Programming in Scala относительно признака App:

Код между фигурными скобками собирается в первичный конструктор одноэлементного объекта и выполняется при инициализации класса.

Версия 2.9.0 также отмечает это:

Объекты, наследующие трейт App, вместо этого используют функцию отложенной инициализации Scala 2.9 для выполнения всего тела как части унаследованного основного метода.

Таким образом, Console.println(x) не выполняется до тех пор, пока не будет запущен Strange, где вы получите это как вывод:

scala> s.main(Array[String]())
0

Если вы добавите еще один Console.println(x) после val x = 42, он распечатает:

scala> s.main(Array[String]())
0
42

Компилятор знает, что x существует в текущей области, но он откладывает его оценку до тех пор, пока он не будет выполнен, а затем выводит значение по умолчанию для Int, которое равно 0.

22.11.2012
  • И App, и object здесь не проблема. Я могу воспроизвести точно такое же поведение с помощью конструктора class без каких-либо трейтов. Таким образом, поведение, которое вы указываете, похоже, относится к любому коду, подобному конструктору. 22.11.2012

  • 2

    Ответ, по сути, заключается в том, как этот компилятор строит вещи. Поскольку в Scala нет статических переменных, инициализация в основном происходит в конструкторе, поэтому Java-эквивалент вашего второго примера будет похож на:

    public class Strange {
        int x = 0;
    
        public Strange() {
            System.out.println(x);
            x = 42;
        }
    }
    

    Что будет нормально компилироваться. Я предполагаю, что компилятор устанавливает значение неинициализированного int равным 0, чтобы избежать NPE. Если вы поменяли порядок операторов в конструкторе на противоположный, вы получите поведение, описанное в первом примере.

    В этом вопросе есть более подробная информация: Scala и прямые ссылки

    22.11.2012
  • Это на самом деле не отвечает почему это так. Такое поведение ломает многие ожидания (ожидание ошибки компиляции в случае отсутствия имени переменной, vals, изменяющего свои значения во время выполнения, вопреки контракту с постоянным значением, одинаковое поведение кода в конструкторах и обычных методах), и я не вижу в этом ничего хорошего. приносит в мир. Может быть, есть что-то, что я упускаю, почему необходимо обрабатывать это таким образом? 22.11.2012
  • Поскольку объекты не используют статические значения, они могут быть переопределены в подклассах и в остальном пользоваться всеми преимуществами объектно-ориентированного проектирования. Из-за этого объект должен быть размещен в памяти, а затем его значение инициализируется соответствующим конструктором, в отличие от статического значения, которое нельзя переопределить и которое можно установить напрямую. Я могу немного ошибаться, так как я никогда не вникал в дизайн компилятора, но я считаю, что суть в этом. Хотя я не могу много говорить об ожиданиях. 22.11.2012
  • Новые материалы

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

    Как написать эффективное резюме
    Предложения по дизайну и макету, чтобы представить себя профессионально Вам не позвонили на собеседование после того, как вы несколько раз подали заявку на работу своей мечты? У вас может..

    Частный метод Python: улучшение инкапсуляции и безопасности
    Введение Python — универсальный и мощный язык программирования, известный своей простотой и удобством использования. Одной из ключевых особенностей, отличающих Python от других языков, является..

    Как я автоматизирую тестирование с помощью Jest
    Шутка для победы, когда дело касается автоматизации тестирования Одной очень важной частью разработки программного обеспечения является автоматизация тестирования, поскольку она создает..

    Работа с векторными символическими архитектурами, часть 4 (искусственный интеллект)
    Hyperseed: неконтролируемое обучение с векторными символическими архитектурами (arXiv) Автор: Евгений Осипов , Сачин Кахавала , Диланта Хапутантри , Тимал Кемпития , Дасвин Де Сильва ,..

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

    Обеспечение масштабируемости LLM: облачный анализ с помощью AWS Fargate и Copilot
    В динамичной области искусственного интеллекта все большее распространение получают модели больших языков (LLM). Они жизненно важны для различных приложений, таких как интеллектуальные..