Семантика рекурсивной перегрузки в языках Scala REPL - JVM - PullRequest
8 голосов
/ 23 сентября 2008

Использование командной строки 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? (Я знаю о блоге Джона Роуза и книге Прагматика языка программирования ... но это все. :)

Ответы [ 4 ]

11 голосов
/ 23 сентября 2008

Проблема связана с тем, что интерпретатору чаще всего приходится заменять существующих элементов с заданным именем, а не перегружать их. Например, я часто буду экспериментировать с чем-то, часто создавая метод с именем 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, это осознанное дизайнерское решение. Как уже упоминалось в сообщении об ошибке, вы все равно можете перегружаться, если вы находитесь не в корневой области видимости. Заключение ваших методов тестирования в класс кажется мне лучшим решением.

5 голосов
/ 08 сентября 2010
% 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")
()
4 голосов
/ 23 сентября 2008

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

1 голос
/ 30 июня 2012

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

Проектные решения, которые приводят к этому:

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

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

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

Начиная с 4-го элемента, A 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» из предыдущих пакетов, а импорт может дублировать друг друга, что позволяет «переопределять» материал.

...