Как стирание типов в Scala работает для параметров типа более высокого порядка?

Я не понимаю, какие параметры универсального типа стирает Scala. Раньше я думал, что он должен стереть все параметры универсального типа, но, похоже, это не так.

Поправьте меня, если я ошибаюсь: если я создаю экземпляр типа Map[Int, String] в коде, то во время выполнения экземпляр знает только то, что он имеет тип Map[_, _], и ничего не знает о параметрах его универсального типа. Вот почему следующее успешно компилируется и выполняется без ошибок:

val x: Map[Int, String] = Map(2 -> "a")
val y: Map[String, Int] = x.asInstanceOf[Map[String, Int]]

Теперь я ожидаю, что все параметры типа более высокого порядка также будут удалены, то есть, если у меня есть

class Foo[H[_, _], X, Y]

Я ожидал, что экземпляр типа Foo[Map, Int, String] ничего не знает о Map. Но теперь рассмотрим следующую последовательность "экспериментов" с приведением типов:

import scala.language.higherKinds

def cast1[A](a: Any): A = a.asInstanceOf[A]
def cast2[H[_, _], A, B](a: Any) = a.asInstanceOf[H[A, B]]
def cast3[F[_[_, _], _, _], H[_, _], X, Y](a: Any) = a.asInstanceOf[F[H, X, Y]]
class CastTo[H[_, _], A, B] {
  def cast(x: Any): H[A, B] = x.asInstanceOf[H[A, B]]
}

ignoreException {
  val v1 = cast1[String](List[Int](1, 2, 3))                      
  // throws ClassCastException
}

ignoreException {
  val v2 = cast2[Map, Int, Long](Map[String, Double]("a" -> 1.0)) 
  // doesn't complain at all, `A` and `B` erased
}

ignoreException {
  // right kind
  val v3 = cast2[Map, Int, Long]((x: Int) => x.toLong)            
  // throws ClassCastException
}

ignoreException {
  // wrong kind
  val v4 = cast2[Map, Int, Long]("wrong kind")                     
  // throws ClassCastException
}

ignoreException {
  class Foo[H[_, _], X, Y](h: H[X, Y])
  val x = new Foo[Function, Int, String](n => "#" * n)
  val v5 = cast3[Foo, Map, Int, Long](x)
  // nothing happens, happily replaces `Function` by `Map`
}

ignoreException {
  val v6 = (new CastTo[Map, Int, Long]).cast(List("hello?"))       
  // throws ClassCastException
}

ignoreException {
  val castToMap = new CastTo[Map, Int, Long]
  val v7 = castToMap.cast("how can it detect this?")               
  // throws ClassCastException
}

ignoreException {
  val castToMap = new CastTo[Map, Int, Long]
  val madCast = castToMap.asInstanceOf[CastTo[Function, Float, Double]]
  val v8 = madCast.cast("what does it detect at all?")
  // String cannot be cast to Function???
  // Why does it retain any information about `Function` here?
}


// --------------------------------------------------------------------
var ignoreBlockCounter = 0
/** Executes piece of code, 
  * catches an exeption (if one is thrown),
  * prints number of `ignoreException`-wrapped block,
  * prints name of the exception.
  */
def ignoreException[U](f: => U): Unit = {
  ignoreBlockCounter += 1
  try {
    f
  } catch {
    case e: Exception =>
      println("[" + ignoreBlockCounter + "]" + e)
  }
}

Вот результат (scala -version 2.12.4):

[1]java.lang.ClassCastException: scala.collection.immutable.$colon$colon cannot be cast to java.lang.String
[3]java.lang.ClassCastException: Main$$anon$1$$Lambda$143/1744347043 cannot be cast to scala.collection.immutable.Map
[4]java.lang.ClassCastException: java.lang.String cannot be cast to scala.collection.immutable.Map
[6]java.lang.ClassCastException: scala.collection.immutable.$colon$colon cannot be cast to scala.collection.immutable.Map
[7]java.lang.ClassCastException: java.lang.String cannot be cast to scala.collection.immutable.Map
[8]java.lang.ClassCastException: java.lang.String cannot be cast to scala.Function1
  • Случаи 1, 3, 4 показывают, что asInstanceOf[Foo[...]] действительно заботится о Foo, это ожидается.
  • Случай 2 указывает, что asInstanceOf[Foo[X,Y]] не заботится о X и Y, это также ожидается.
  • Случай 5 указывает, что asInstanceOf не заботится о параметре более высокого родства Map, аналогично случаю 2, это также ожидается.

Все идет нормально. Однако случаи 6, 7, 8 предполагают другое поведение: здесь экземпляр типа CastTo[Foo, X, Y], кажется, по какой-то причине сохраняет информацию о параметре универсального типа Foo. Точнее, CastTo[Map, Int, Long], кажется, несет с собой достаточно информации, чтобы знать, что строка не может быть преобразована в Map. Более того, в случае 8 он, кажется, даже изменил Map на Function из-за приведения.

Вопросов):

  1. Правильно ли я понимаю, что первый общий параметр CastTo не удален, или есть что-то еще, чего я не вижу? Какая-то неявная операция или что-нибудь?
  2. Есть ли документация, описывающая это поведение?
  3. Есть ли причина, по которой я должен хотеть такого поведения? Я нахожу это несколько нелогичным, но, может быть, это всего лишь я, и я неправильно использую инструмент ...

Спасибо за прочтение.

РЕДАКТИРОВАТЬ: изучение похожих примеров выявило проблему с компилятором 2.12.4 (см. мой собственный «ответ» ниже), но это отдельная проблема.


person Andrey Tyukin    schedule 25.01.2018    source источник


Ответы (2)


Я думаю, вы кое-что путаете.

Приведение к универсальным типам откладывается до тех пор, пока типы не станут конкретными. Например, возьмите этот фрагмент кода:

class CastTo[H[_, _], A, B] {
  def cast(x: Any): H[A, B] = x.asInstanceOf[H[A, B]]
}

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

class CastTo {
  def cast(x: Object): Object = x
}

Затем позже в коде вы дадите String методу cast, и компилятор увидит, что в соответствии с имеющейся у него информацией о типе выйдет Map[Int, Long]. Но в байт-коде cast есть стертый возвращаемый тип Object, поэтому компилятор должен вставить приведение в use-site метода cast. Этот код

val castToMap = new CastTo[Map, Int, Long]
val v7 = castToMap.cast("how can it detect this?")

будет в байт-коде примерно эквивалентен следующему (псевдо) коду:

val castToMap = new CastTo
val v7 = castToMap.cast("how can it detect this?").asInstanceOf[Map]

Что касается других ваших вопросов:

  1. Не то чтобы я сразу узнал об этом.
  2. Почему бы вам не этого захотеть? Вы меняете String на Map[Int, Long]. Это неизбежно в конце концов рухнет. Отказ (относительно) быстро с ClassCastException, вероятно, самый безопасный и удобный вариант.
person Jasper-M    schedule 25.01.2018
comment
поэтому компилятор должен вставить приведение в use-site метода приведения - это была недостающая часть. Спасибо, что указали на различие между сайтом определения и сайтом использования в этом случае. - person Andrey Tyukin; 25.01.2018

Хорошо, вероятно, это не ожидаемое поведение.

Вот случайная версия вышеизложенного:

ignoreException {
  class Foo[X, Y]
  class Bar[X, Y]

  val castToMap = new CastTo[Map, Int, Long]
  val madderCast = if (math.random() > 0.5) {
    println("Foo")
    castToMap.asInstanceOf[CastTo[Foo, Float, Double]]
  } else {
    println("Bar")
    castToMap.asInstanceOf[CastTo[Bar, String, Any]]
  }
  val v9 = madderCast.cast("crash 'dis serva, awww yeah!")
  // crashes the compile server for scala 2.12.4
  // dotty 0.4.0-RC1 doesn't complain at all (as expected!)
}

вызывает сбой компилятора 2.12.4. У Дотти нет проблем.

РЕДАКТИРОВАТЬ Более короткий автономный пример:

импортировать scala.language.higherKinds

class Foo[X]
class Bar[X]
class CrashIt[A[_]] {
  def cast(a: Any): A[Int] = a.asInstanceOf[A[Int]]
}
val c = if (true) new CrashIt[Foo] else new CrashIt[Bar]
val x = c.cast("")

Вылетает с:

java.util.NoSuchElementException: head of empty list
    at scala.collection.immutable.Nil$.head(List.scala:428)
    at scala.collection.immutable.Nil$.head(List.scala:425)
    at scala.tools.nsc.typechecker.ContextErrors$InferencerContextErrors$InferErrorGen$.
      NotWithinBoundsErrorMessage(ContextErrors.scala:1045)
    at scala.tools.nsc.typechecker.ContextErrors$InferencerContextErrors$InferErrorGen$.
      NotWithinBoundsContextErrors.scala:1052)
    at scala.tools.nsc.typechecker.Infer$Inferencer.issueBoundsError$1(Infer.scala:881)
    at scala.tools.nsc.typechecker.Infer$Inferencer.check$1(Infer.scala:887)
    at scala.tools.nsc.typechecker.Infer$Inferencer.checkBounds(Infer.scala:891)
    at scala.tools.nsc.typechecker.RefChecks$RefCheckTransformer.checkBounds(RefChecks.scala:1203)
    at scala.tools.nsc.typechecker.RefChecks$RefCheckTransformer.checkTypeRef(RefChecks.scala:1384)
    at scala.tools.nsc.typechecker.RefChecks$RefCheckTransformer.$anonfun$transform$4(RefChecks.scala:1668)
    at scala.tools.nsc.typechecker.RefChecks$RefCheckTransformer.$anonfun$transform$4$adapted(RefChecks.scala:1659)
    at scala.reflect.internal.tpe.TypeMaps$ForEachTypeTraverser.traverse(TypeMaps.scala:1102)
    at scala.reflect.internal.Types$Type.foreach(Types.scala:787)
    at scala.tools.nsc.typechecker.RefChecks$RefCheckTransformer.transform(RefChecks.scala:1407)
    at scala.tools.nsc.typechecker.RefChecks$RefCheckTransformer.transform(RefChecks.scala:107)
    at scala.reflect.internal.Trees.$anonfun$itransform$1(Trees.scala:1369)
    at scala.reflect.api.Trees$Transformer.atOwner(Trees.scala:2600)
    at scala.reflect.internal.Trees.itransform(Trees.scala:1368)
    at scala.reflect.internal.Trees.itransform$(Trees.scala:1348)
    at scala.reflect.internal.SymbolTable.itransform(SymbolTable.scala:16)
    at scala.reflect.internal.SymbolTable.itransform(SymbolTable.scala:16)
    at scala.reflect.api.Trees$Transformer.transform(Trees.scala:2555)
    at scala.tools.nsc.typechecker.RefChecks$RefCheckTransformer.transform(RefChecks.scala:1751)
    at scala.tools.nsc.typechecker.RefChecks$RefCheckTransformer.transformStat(RefChecks.scala:1186)
    at scala.tools.nsc.typechecker.RefChecks$RefCheckTransformer.$anonfun$transformStats$1(RefChecks.scala:1169)
    at scala.tools.nsc.typechecker.RefChecks$RefCheckTransformer.transformStats(RefChecks.scala:1169)
    at scala.tools.nsc.typechecker.RefChecks$RefCheckTransformer.transformStats(RefChecks.scala:107)
    at scala.reflect.internal.Trees.itransform(Trees.scala:1416)
    at scala.reflect.internal.Trees.itransform$(Trees.scala:1348)
    at scala.reflect.internal.SymbolTable.itransform(SymbolTable.scala:16)
    at scala.reflect.internal.SymbolTable.itransform(SymbolTable.scala:16)
    at scala.reflect.api.Trees$Transformer.transform(Trees.scala:2555)
    at scala.tools.nsc.typechecker.RefChecks$RefCheckTransformer.transform(RefChecks.scala:1751)
    at scala.tools.nsc.typechecker.RefChecks$RefCheckTransformer.transform(RefChecks.scala:107)
    at scala.reflect.api.Trees$Transformer.transformTemplate(Trees.scala:2563)
    at scala.reflect.internal.Trees.$anonfun$itransform$4(Trees.scala:1420)
    at scala.reflect.api.Trees$Transformer.atOwner(Trees.scala:2600)
    at scala.reflect.internal.Trees.itransform(Trees.scala:1419)
    at scala.reflect.internal.Trees.itransform$(Trees.scala:1348)
    at scala.reflect.internal.SymbolTable.itransform(SymbolTable.scala:16)
    at scala.reflect.internal.SymbolTable.itransform(SymbolTable.scala:16)
    at scala.reflect.api.Trees$Transformer.transform(Trees.scala:2555)
    at scala.tools.nsc.typechecker.RefChecks$RefCheckTransformer.transform(RefChecks.scala:1751)
    at scala.tools.nsc.typechecker.RefChecks$RefCheckTransformer.transformStat(RefChecks.scala:1198)
    at scala.tools.nsc.typechecker.RefChecks$RefCheckTransformer.$anonfun$transformStats$1(RefChecks.scala:1169)
    at scala.tools.nsc.typechecker.RefChecks$RefCheckTransformer.transformStats(RefChecks.scala:1169)
    at scala.tools.nsc.typechecker.RefChecks$RefCheckTransformer.transformStats(RefChecks.scala:107)
    at scala.reflect.internal.Trees.itransform(Trees.scala:1378)
    at scala.reflect.internal.Trees.itransform$(Trees.scala:1348)
    at scala.reflect.internal.SymbolTable.itransform(SymbolTable.scala:16)
    at scala.reflect.internal.SymbolTable.itransform(SymbolTable.scala:16)
    at scala.reflect.api.Trees$Transformer.transform(Trees.scala:2555)
    at scala.tools.nsc.typechecker.RefChecks$RefCheckTransformer.transform(RefChecks.scala:1751)
    at scala.tools.nsc.typechecker.RefChecks$RefCheckTransformer.transform(RefChecks.scala:107)
    at scala.reflect.internal.Trees.$anonfun$itransform$2(Trees.scala:1375)
    at scala.reflect.api.Trees$Transformer.atOwner(Trees.scala:2600)
    at scala.reflect.internal.Trees.itransform(Trees.scala:1373)
    at scala.reflect.internal.Trees.itransform$(Trees.scala:1348)
    at scala.reflect.internal.SymbolTable.itransform(SymbolTable.scala:16)
    at scala.reflect.internal.SymbolTable.itransform(SymbolTable.scala:16)
    at scala.reflect.api.Trees$Transformer.transform(Trees.scala:2555)
    at scala.tools.nsc.typechecker.RefChecks$RefCheckTransformer.transform(RefChecks.scala:1751)
    at scala.tools.nsc.typechecker.RefChecks$RefCheckTransformer.transformStat(RefChecks.scala:1198)
    at scala.tools.nsc.typechecker.RefChecks$RefCheckTransformer.$anonfun$transformStats$1(RefChecks.scala:1169)
    at scala.tools.nsc.typechecker.RefChecks$RefCheckTransformer.transformStats(RefChecks.scala:1169)
    at scala.tools.nsc.typechecker.RefChecks$RefCheckTransformer.transformStats(RefChecks.scala:107)
    at scala.reflect.internal.Trees.itransform(Trees.scala:1416)
    at scala.reflect.internal.Trees.itransform$(Trees.scala:1348)
    at scala.reflect.internal.SymbolTable.itransform(SymbolTable.scala:16)
    at scala.reflect.internal.SymbolTable.itransform(SymbolTable.scala:16)
    at scala.reflect.api.Trees$Transformer.transform(Trees.scala:2555)
    at scala.tools.nsc.typechecker.RefChecks$RefCheckTransformer.transform(RefChecks.scala:1751)
    at scala.tools.nsc.typechecker.RefChecks$RefCheckTransformer.transform(RefChecks.scala:107)
    at scala.reflect.api.Trees$Transformer.transformTemplate(Trees.scala:2563)
    at scala.reflect.internal.Trees.$anonfun$itransform$5(Trees.scala:1425)
    at scala.reflect.api.Trees$Transformer.atOwner(Trees.scala:2600)
    at scala.reflect.internal.Trees.itransform(Trees.scala:1424)
    at scala.reflect.internal.Trees.itransform$(Trees.scala:1348)
    at scala.reflect.internal.SymbolTable.itransform(SymbolTable.scala:16)
    at scala.reflect.internal.SymbolTable.itransform(SymbolTable.scala:16)
    at scala.reflect.api.Trees$Transformer.transform(Trees.scala:2555)
    at scala.tools.nsc.typechecker.RefChecks$RefCheckTransformer.transform(RefChecks.scala:1751)
    at scala.tools.nsc.typechecker.RefChecks$RefCheckTransformer.transformStat(RefChecks.scala:1198)
    at scala.tools.nsc.typechecker.RefChecks$RefCheckTransformer.$anonfun$transformStats$1(RefChecks.scala:1169)
    at scala.tools.nsc.typechecker.RefChecks$RefCheckTransformer.transformStats(RefChecks.scala:1169)
    at scala.tools.nsc.typechecker.RefChecks$RefCheckTransformer.transformStats(RefChecks.scala:107)
    at scala.reflect.internal.Trees.$anonfun$itransform$7(Trees.scala:1438)
    at scala.reflect.api.Trees$Transformer.atOwner(Trees.scala:2600)
    at scala.reflect.internal.Trees.itransform(Trees.scala:1438)
    at scala.reflect.internal.Trees.itransform$(Trees.scala:1348)
    at scala.reflect.internal.SymbolTable.itransform(SymbolTable.scala:16)
    at scala.reflect.internal.SymbolTable.itransform(SymbolTable.scala:16)
    at scala.reflect.api.Trees$Transformer.transform(Trees.scala:2555)
    at scala.tools.nsc.typechecker.RefChecks$RefCheckTransformer.transform(RefChecks.scala:1751)
    at scala.tools.nsc.typechecker.RefChecks$RefCheckTransformer.transform(RefChecks.scala:107)
    at scala.tools.nsc.ast.Trees$Transformer.transformUnit(Trees.scala:140)
    at scala.tools.nsc.transform.Transform$Phase.apply(Transform.scala:30)
    at scala.tools.nsc.Global$GlobalPhase.$anonfun$applyPhase$1(Global.scala:436)
    at scala.tools.nsc.Global$GlobalPhase.applyPhase(Global.scala:429)
    at scala.tools.nsc.Global$GlobalPhase.$anonfun$run$1(Global.scala:400)
    at scala.tools.nsc.Global$GlobalPhase.$anonfun$run$1$adapted(Global.scala:400)
    at scala.collection.Iterator.foreach(Iterator.scala:929)
    at scala.collection.Iterator.foreach$(Iterator.scala:929)
    at scala.collection.AbstractIterator.foreach(Iterator.scala:1417)
    at scala.tools.nsc.Global$GlobalPhase.run(Global.scala:400)
    at scala.tools.nsc.Global$Run.compileUnitsInternal(Global.scala:1452)
    at scala.tools.nsc.Global$Run.compileUnits(Global.scala:1436)
    at scala.tools.nsc.Global$Run.compileSources(Global.scala:1429)
    at scala.tools.nsc.Global$Run.compile(Global.scala:1545)
    at scala.tools.nsc.StandardCompileServer.session(CompileServer.scala:150)
    at scala.tools.util.SocketServer.$anonfun$doSession$2(SocketServer.scala:80)
    at scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.java:12)
    at scala.util.DynamicVariable.withValue(DynamicVariable.scala:58)
    at scala.Console$.withOut(Console.scala:163)
    at scala.tools.util.SocketServer.$anonfun$doSession$1(SocketServer.scala:80)
    at scala.tools.util.SocketServer.$anonfun$doSession$1$adapted(SocketServer.scala:75)
    at scala.tools.nsc.io.Socket.applyReaderAndWriter(Socket.scala:49)
    at scala.tools.util.SocketServer.doSession(SocketServer.scala:75)
    at scala.tools.util.SocketServer.loop$1(SocketServer.scala:91)
    at scala.tools.util.SocketServer.run(SocketServer.scala:103)
    at scala.tools.nsc.CompileServer$.$anonfun$execute$3(CompileServer.scala:216)
    at scala.runtime.java8.JFunction0$mcZ$sp.apply(JFunction0$mcZ$sp.java:12)
    at scala.util.DynamicVariable.withValue(DynamicVariable.scala:58)
    at scala.Console$.withOut(Console.scala:163)
    at scala.tools.nsc.CompileServer$.$anonfun$execute$2(CompileServer.scala:211)
    at scala.runtime.java8.JFunction0$mcZ$sp.apply(JFunction0$mcZ$sp.java:12)
    at scala.util.DynamicVariable.withValue(DynamicVariable.scala:58)
    at scala.Console$.withErr(Console.scala:192)
    at scala.tools.nsc.CompileServer$.main(CompileServer.scala:211)
    at scala.tools.nsc.CompileServer.main(CompileServer.scala)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at scala.reflect.internal.util.ScalaClassLoader.$anonfun$run$2(ScalaClassLoader.scala:99)
    at scala.reflect.internal.util.ScalaClassLoader.asContext(ScalaClassLoader.scala:34)
    at scala.reflect.internal.util.ScalaClassLoader.asContext$(ScalaClassLoader.scala:30)
    at scala.reflect.internal.util.ScalaClassLoader$URLClassLoader.asContext(ScalaClassLoader.scala:125)
    at scala.reflect.internal.util.ScalaClassLoader.run(ScalaClassLoader.scala:99)
    at scala.reflect.internal.util.ScalaClassLoader.run$(ScalaClassLoader.scala:91)
    at scala.reflect.internal.util.ScalaClassLoader$URLClassLoader.run(ScalaClassLoader.scala:125)
    at scala.tools.nsc.CommonRunner.run(ObjectRunner.scala:22)
    at scala.tools.nsc.CommonRunner.run$(ObjectRunner.scala:21)
    at scala.tools.nsc.ObjectRunner$.run(ObjectRunner.scala:39)
    at scala.tools.nsc.CommonRunner.runAndCatch(ObjectRunner.scala:29)
    at scala.tools.nsc.CommonRunner.runAndCatch$(ObjectRunner.scala:28)
    at scala.tools.nsc.ObjectRunner$.runAndCatch(ObjectRunner.scala:39)
    at scala.tools.nsc.MainGenericRunner.runTarget$1(MainGenericRunner.scala:66)
    at scala.tools.nsc.MainGenericRunner.run$1(MainGenericRunner.scala:85)
    at scala.tools.nsc.MainGenericRunner.process(MainGenericRunner.scala:96)
    at scala.tools.nsc.MainGenericRunner$.main(MainGenericRunner.scala:101)
    at scala.tools.nsc.MainGenericRunner.main(MainGenericRunner.scala)    

Шаг литья кажется решающим. Если вызов cast удален, ничего не происходит.

Этого не происходит, если параметр A не имеет более высокого порядка, поэтому, например, для class CrashIt[A] и new CrashIt[Int] компилятор не жалуется.

РЕДАКТИРОВАТЬ-2: не удалось найти эту точную ошибку (или ее очевидное обобщение). Сообщите об этом здесь: https://github.com/scala/bug/issues/10700.

person Andrey Tyukin    schedule 25.01.2018
comment
Вероятно, это ошибка компилятора, потому что она никогда не должна давать сбой (хотя некоторые StackOverflowErrors могут быть исключениями из этого правила), но не связанные с приведением типов. Вы получите то же самое с val c = if (true) new CastTo[Foo, Float, Double] else new CastTo[Bar, Float, Double]. - person Jasper-M; 25.01.2018
comment
Причина, по которой Dotty не падает, отчасти заключается в том, что он может представлять больше типов, а его система типов, вероятно, имеет более прочную основу. Причина, по которой он не генерирует ClassCastException, заключается в том, что v9 по-прежнему будет иметь более или менее общий тип, который JVM не может представлять, поэтому его нужно стереть до Object. - person Jasper-M; 25.01.2018
comment
@ Jasper-M, это явно ошибка компилятора. Я еще не пытался сделать код как можно короче, но ваш пример с двумя new CastTo, как ни странно, работает нормально. Я подозреваю, что решение этой проблемы - это что-то вроде изобретения более прочного фундамента, реализации Dotty:] - person Andrey Tyukin; 25.01.2018
comment
Странный. Когда я компилирую свой фрагмент в REPL, он вылетает из-за StackOverflowError. - person Jasper-M; 25.01.2018
comment
Когда я запускаю версию if (...) ... asInstanceOf, она вылетает с java.util.NoSuchElementException: head пустого списка. Это немного другой способ сбоя: вместо того, чтобы бесконечно повторяться и надеяться на более светлое будущее, он заканчивается очень конкретным списком, который не должен быть пустым, а должен быть пустым. Я напишу сжатый пример, который приводит к ошибке. - person Andrey Tyukin; 25.01.2018
comment
Ты прав. Вот тот, который вылетает с тем же исключением: scastie.scala-lang.org/Jasper-M / 7vEZQdAXSlCj0Wb8GBlBpA У меня произошел сбой REPL по какой-то странной причине. - person Jasper-M; 25.01.2018