Nil и List как регистр выражений в Scala - PullRequest
11 голосов
/ 26 сентября 2011

Этот код компилируется:

def wtf(arg: Any) = {  
  arg match {  
    case Nil => "Nil was passed to arg"  
    case List() => "List() was passed to arg"  
    case _ =>"otherwise"  
  }  
}

Но этот код не:

def wtf(arg: Any) = {  
  arg match {  
    case List() => "List() was passed to arg"  
    case Nil => "Nil was passed to arg"  
    case _ =>"otherwise"  
  }  
}  

Строка case Nil => ... помечена как недоступнаякод.Почему в первом случае строка case List () => ... не помечена с той же ошибкой?

Ответы [ 4 ]

9 голосов
/ 29 сентября 2011

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

1) case List () вызывает экстрактор, для которого в общем случае проверка исчерпываемости / недоступности невозможнаслучай, потому что экстракторы вызывают произвольные функции.Пока все хорошо, нельзя ожидать, что мы решим проблему остановки.

2) Еще в более "дикие западные" дни компилятора было решено, что сопоставление с образцом может быть ускореномного (и не теряйте исчерпывающую проверку), если «case List ()» был просто переведен в «case Nil» на ранней стадии компиляции, чтобы избежать извлечения.Это все еще так, и, хотя это может быть отменено, очевидно, многим людям сказали, что "case List () =>" совершенно нормально, и мы не хотим внезапно пессимизировать весь их код.Так что мне просто нужно найти выход.

Эмпирически можно увидеть, что List имеет привилегию, попробовав его с другим классом.Нет ошибок недоступности.

import scala.collection.immutable.IndexedSeq
val Empty: IndexedSeq[Nothing] = IndexedSeq()
def wtf1(arg: Any) = {  
  arg match {  
    case Empty => "Nil was passed to arg"  
    case IndexedSeq() => "IndexedSeq() was passed to arg"  
    case _ =>"otherwise"  
  }  
}

def wtf2(arg: Any) = {  
  arg match {  
    case IndexedSeq() => "IndexedSeq() was passed to arg"  
    case Empty => "Nil was passed to arg"  
    case _ =>"otherwise"  
  }  
}  
6 голосов
/ 26 сентября 2011

Это несоответствие особенно странно, поскольку код для случая Nil во второй версии определенно не недоступен, как мы видим, если немного скрыть что-то от компилятора:

def wtf(arg: Any) = {
  arg match {
    case List() => "List() was passed to arg"  
    case x => x match {
      case Nil => "Nil was passed to arg"
      case _ =>"otherwise"
    }
  }
}

Теперь wtf(Vector()) вернется "Nil was passed to arg". Это также может показаться нелогичным, но это потому, что литеральные шаблоны соответствуют значениям, которые равны в терминах == и Vector() == Nil, но Vector() не соответствует шаблону экстрактора List().

Более кратко:

scala> (Vector(): Seq[_]) match { case List() => true; case Nil => false }
<console>:8: error: unreachable code

scala> (Vector(): Seq[_]) match { case List() => true; case x => x match { case Nil => false } }
res0: Boolean = false

Таким образом, ответ компилятора полностью противоположен: в «хорошей» версии второй случай недоступен, а в «плохой» версии второй вариант совершенно нормально. Я сообщил об этом как об ошибке (SI-5029) .

5 голосов
/ 26 сентября 2011

Nil - это объект, расширяющий List[Nothing].Будучи более конкретным, чем List(), он не будет достигнут, если он появится после List() в выражении дела.

Хотя я думаю, что вышеизложенное более или менее верно, скорее всего, это не вся история.

В статье есть подсказки Сопоставление объектов с шаблонами , хотя я не вижу однозначного ответа.

Я подозреваю, что обнаружение недоступности просто более полно реализованодля именованных констант и литералов, чем для шаблонов-конструкторов, и что List() интерпретируется как шаблон-конструктор (даже если он тривиальный), тогда как Nil является именованной константой.

3 голосов
/ 26 сентября 2011

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

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

scala -Xprint:typer предполагает, что Nil является буквенным шаблоном , использующим immutable.this.Nil.== для проверки на совпадение, в то время как List() является шаблоном экстрактора . Глядя на реализацию экстрактора в нем, похоже, делает что-то вроде this :

def unapplySeq[A](x: CC[A]): Some[CC[A]] = Some(x)

Так что мне стало любопытно и выкатили альтернативную реализацию:

object ListAlt {
  def unapplySeq[A](l: List[A]): Some[List[A]] = Some(l)
}

Это соответствует List():

scala> Nil match { case ListAlt() => 1 }
res0: Int = 1

Но если я реализую вашу функцию с ней, она прекрасно скомпилируется (за исключением непроверенного предупреждения):

def f(a: Any) = a match { case ListAlt() => 1 case Nil => 2 case _ => 0 }

scala> f(List())
res2: Int = 1

scala> f(Nil)
res3: Int = 1

scala> f(4)
res4: Int = 0

Поэтому мне интересно, есть ли в реализации средства сопоставления с образцом какой-то специальный регистр для Nil и List(). Может быть, он обрабатывает List () как литерал ...

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...