Семантика рекурсивной перегрузки в Scala REPL - языки JVM

Используя командную строку Scala REPL:

def foo(x: Int): Unit = {}
def foo(x: String): Unit = {println(foo(2))}

дает

error: type mismatch;
found: Int(2)
required: String

Кажется, что вы не можете определить перегруженные рекурсивные методы в REPL. Я подумал, что это ошибка в Scala REPL, и зарегистрировал ее, но она была почти мгновенно закрыта с помощью "wontfix: я не вижу никакого способа поддержать это, учитывая семантику интерпретатора, потому что эти два метода должны быть скомпилированы. все вместе." Он рекомендовал поместить методы во включающий объект.

Есть ли реализация языка JVM или эксперт по Scala, который мог бы объяснить, почему? Я вижу, если бы методы вызывали друг друга, например, было бы проблемой, но в этом случае?

Или, если это слишком большой вопрос, и вы думаете, что мне нужно больше предварительных знаний, есть ли у кого-нибудь хорошие ссылки на книги или сайты о языковых реализациях, особенно на JVM? (Я знаю о блоге Джона Роуза и книге Programming Language Pragmatics ... но это все. :)

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
8
0
722
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

REPL примет, если вы скопируете обе строки и вставите их одновременно.

Ответ принят как подходящий

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

def test(x: Int) = x + x

Чуть позже допустим, что я провожу эксперимент разные и создаю другой метод с именем test, не связанный с первым:

def test(ls: List[Int]) = (0 /: ls) { _ + _ }

Это не совсем нереальный сценарий. Фактически, именно так большинство людей используют интерпретатор, часто даже не осознавая этого. Если интерпретатор произвольно решил оставить обе версии test в области видимости, это могло привести к запутанным семантическим различиям в использовании теста. Например, мы могли бы вызвать на test, случайно передавая Int, а не List[Int] (не самая маловероятная авария в мире):

test(1 :: Nil)  // => 1
test(2)         // => 4  (expecting 2)

Со временем корневая область видимости интерпретатора будет невероятно загромождена различными версиями методов, полей и т. д. Я обычно оставляю свой интерпретатор открытым на несколько дней, но если бы такая перегрузка была разрешена, мы были бы вынуждены: очищайте "интерпретатор" так часто, как это может сбивать с толку.

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

% scala28
Welcome to Scala version 2.8.0.final (Java HotSpot(TM) 64-Bit Server VM, Java 1.6.0_20).
Type in expressions to have them evaluated.
Type :help for more information.

scala> def foo(x: Int): Unit = () ; def foo(x: String): Unit = { println(foo(2)) } 
foo: (x: String)Unit <and> (x: Int)Unit
foo: (x: String)Unit <and> (x: Int)Unit

scala> foo(5)

scala> foo("abc")
()

Как показывает ответ импровизированный, возможна перегрузка. Комментарий Дэниел о дизайнерском решении правильный, но, на мой взгляд, неполный и немного вводящий в заблуждение. объявленный вне закона перегрузок отсутствует (поскольку они возможны), но их нелегко достичь.

К этому приводят следующие дизайнерские решения:

  1. Все предыдущие определения должны быть доступны.
  2. Компилируется только недавно введенный код, вместо того, чтобы перекомпилировать все, что когда-либо вводилось каждый раз.
  3. Должна быть возможность переопределить определения (как сказал Даниил).
  4. Должна быть возможность определять такие члены, как vals и defs, а не только классы и объекты.

Проблема в том ... как достичь всех этих целей? Как мы обрабатываем ваш пример?

def foo(x: Int): Unit = {}
def foo(x: String): Unit = {println(foo(2))}

Начиная с 4-го элемента, val или def может быть определен только внутри class, trait, object или package object. Итак, REPL помещает определения внутри объектов, как это (не актуальное представление!)

package $line1 { // input line
  object $read { // what was read
    object $iw { // definitions
      def foo(x: Int): Unit = {}
    }
    // val res1 would be here somewhere if this was an expression
  }
}

Теперь, из-за того, как работает JVM, после того, как вы определили один из них, вы не можете их расширить. Конечно, можно было все перекомпилировать, но мы от этого отказались. Поэтому вам нужно разместить его в другом месте:

package $line1 { // input line
  object $read { // what was read
    object $iw { // definitions
      def foo(x: String): Unit = { println(foo(2)) }
    }
  }
}

И это объясняет, почему ваши примеры не являются перегрузками: они определены в двух разных местах. Если вы поместите их в одну строку, все они будут определены вместе, что приведет к их перегрузке, как показано в примере extempore.

Что касается других проектных решений, каждое новое определение импорта пакета и "res" из предыдущих пакетов, и импорт может затенять друг друга, что позволяет "переопределить" материал.

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