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

StackOverflow https://stackoverflow.com/questions/120702

Вопрос

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

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

дает

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

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

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

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

Это было полезно?

Решение

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

def test(x: Int) = x + x

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

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

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

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

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

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

  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)) }
    }
  }
}

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top