2008-09-23 3 views
8

Использования Scala в командной строке РЕПЛА:Рекурсивная перегрузочная семантика в Scala РЕПЛ - языки JVM

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

ответ

11

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

+0

Отличный ответ Даниил, спасибо. Кроме того, мне нравится ваш блог. :) – 2008-09-23 18:27:29

4

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

5
% 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

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

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

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

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

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

Начиная с 4-го пункта, A val или def может быть определена только внутри class, trait, object или package object. Таким образом, РЕПЛ помещает определения внутри объектов, как этот (не фактического представления!)

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

Смежные вопросы