Выбор места неявного преобразования с блоком - PullRequest
4 голосов
/ 20 декабря 2011

В следующем коде неявное преобразование применяется вокруг строки println(2);Я глупо ожидал, что он будет применяться вокруг всего блока { println(1); println(2) }.Как я должен рассуждать о том, где компилятор размещает неявное?

object Executor {

  private var runnable: Runnable = _

  def setRunnable(runnable: Runnable) {
    this.runnable = runnable
  }

  def execute() { runnable.run() }

}

object Run extends App {

  implicit def blockToRunnable(p: ⇒ Any): Runnable =
    new Runnable { def run() = p }

  Executor.setRunnable {
    println(1)
    println(2)
  }

  println("Before execute")
  Executor.execute()

}

Ответы [ 4 ]

4 голосов
/ 20 декабря 2011

Я рационализирую это поведение следующим образом: согласно спецификации, тип блока {s1; s2; ...; sn; e } является типом последнего выражения e.

Таким образом, компилятор принимает e и проверяет типэто против Runnable.Это терпит неудачу, поэтому он ищет неявное преобразование, которое преобразует e в Runnable.Таким образом, это будет выглядеть так:

{ s1; s2; ... sn; convert(e) }

Это подтверждается scala -Xprint:typer на этом небольшом примере:

class A
implicit def convert(a: A): String = a.toString
def f(s: String) { println(s) }
f{ println(1); new A }

печатает:

private[this] val res0: Unit = $line3.$read.$iw.$iw.f({
  scala.this.Predef.println(1);
  $line2.$read.$iw.$iw.convert(new $line1.$read.$iw.$iw.A())
});
3 голосов
/ 20 декабря 2011

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

, если выражение e имеет тип T, а T не соответствует выражению ожидаемого типа pt.В этом случае ищется неявный v, который применим к e и тип результата которого соответствует pt.

В секции 6.11 Блоки ожидаемый тип последнего выражения блока определяется как

Ожидаемый типпоследнее выражение e - ожидаемый тип блока.

Учитывая эту спецификацию, кажется, что компилятор имеет , чтобы вести себя таким образом.Ожидаемый тип блока - Runnable, а ожидаемый тип println(2) также становится Runnable.

Предложение: если вы хотите знать, какие применяются последствия, вы можете использовать каждую ночьпостроить для 2.1 Scala IDE для Eclipse.Он может «подсвечивать последствия».

Отредактировано: Признаюсь, это удивительно, когда в области видимости есть неявный вызов.

0 голосов
/ 31 августа 2012

Мне очень понравилось объяснение, данное в первой головоломке Скала .

Другими словами, что будет выводом:

List(1, 2).map { i => println("Hi"); i + 1 }
List(1, 2).map { println("Hi"); _ + 1 }
0 голосов
/ 20 декабря 2011

Проблема в том, что вы думаете о блоках, как о блоках, как о кусочках кода. Это не так. { a; b; c } - это , а не фрагмент кода, который можно передать.

Итак, как вы должны рассуждать о последствиях? На самом деле, как вы должны рассуждать о представлениях , которые являются неявными преобразованиями. Представления применяются к значению, которое необходимо изменить. В вашем примере значение

{
    println(1)
    println(2)
}

передается на setRunnable. Значением блока является значение его последнего выражения, поэтому он передает результат println(2) в setRunnable. Так как это Unit и setRunnable требует Runnable, то неявное ищется и обнаруживается, поэтому println(2) передается грубо названному blockToRunnable.

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

THERE ARE NO BLOCKS IN SCALA.

Есть функции, но не блоки.

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

...