As far as I understand, the following code snippet should execute fine. Stream("A", "").toIterable.map(_.head)
throws an exception, which should be caught by the try
block.
import org.scalatest.FunSuite
class TryCatchTest extends FunSuite {
test("Try failure") {
List(1).flatMap {
_ =>
try {
Stream("A", "").toIterable.map(_.head)
} catch {
case e: Throwable => Nil
}
}
}
}
But when I run it, it crashes with the following error:
next on empty iterator
java.util.NoSuchElementException: next on empty iterator
at scala.collection.Iterator$$anon$2.next(Iterator.scala:39)
at scala.collection.Iterator$$anon$2.next(Iterator.scala:37)
at scala.collection.IndexedSeqLike$Elements.next(IndexedSeqLike.scala:63)
at scala.collection.IterableLike$class.head(IterableLike.scala:107)
at scala.collection.immutable.StringOps.scala$collection$IndexedSeqOptimized$$super$head(StringOps.scala:30)
at scala.collection.IndexedSeqOptimized$class.head(IndexedSeqOptimized.scala:126)
at scala.collection.immutable.StringOps.head(StringOps.scala:30)
at TryCatchTest$$anonfun$1$$anonfun$apply$mcV$sp$1$$anonfun$apply$1.apply(TryCatchTest.scala:8)
at TryCatchTest$$anonfun$1$$anonfun$apply$mcV$sp$1$$anonfun$apply$1.apply(TryCatchTest.scala:8)
at scala.collection.immutable.Stream$$anonfun$map$1.apply(Stream.scala:418)
at scala.collection.immutable.Stream$$anonfun$map$1.apply(Stream.scala:418)
at scala.collection.immutable.Stream$Cons.tail(Stream.scala:1222)
at scala.collection.immutable.Stream$Cons.tail(Stream.scala:1212)
at scala.collection.immutable.Stream.foreach(Stream.scala:595)
at scala.collection.immutable.List.flatMap(List.scala:327)
at TryCatchTest$$anonfun$1.apply$mcV$sp(TryCatchTest.scala:5)
at TryCatchTest$$anonfun$1.apply(TryCatchTest.scala:5)
at TryCatchTest$$anonfun$1.apply(TryCatchTest.scala:5)
at org.scalatest.Transformer$$anonfun$apply$1.apply$mcV$sp(Transformer.scala:22)
at org.scalatest.OutcomeOf$class.outcomeOf(OutcomeOf.scala:85)
at org.scalatest.OutcomeOf$.outcomeOf(OutcomeOf.scala:104)
at org.scalatest.Transformer.apply(Transformer.scala:22)
at org.scalatest.Transformer.apply(Transformer.scala:20)
at org.scalatest.FunSuiteLike$$anon$1.apply(FunSuiteLike.scala:166)
at org.scalatest.Suite$class.withFixture(Suite.scala:1122)
at org.scalatest.FunSuite.withFixture(FunSuite.scala:1555)
at org.scalatest.FunSuiteLike$class.invokeWithFixture$1(FunSuiteLike.scala:163)
at org.scalatest.FunSuiteLike$$anonfun$runTest$1.apply(FunSuiteLike.scala:175)
at org.scalatest.FunSuiteLike$$anonfun$runTest$1.apply(FunSuiteLike.scala:175)
at org.scalatest.SuperEngine.runTestImpl(Engine.scala:306)
at org.scalatest.FunSuiteLike$class.runTest(FunSuiteLike.scala:175)
at org.scalatest.FunSuite.runTest(FunSuite.scala:1555)
at org.scalatest.FunSuiteLike$$anonfun$runTests$1.apply(FunSuiteLike.scala:208)
at org.scalatest.FunSuiteLike$$anonfun$runTests$1.apply(FunSuiteLike.scala:208)
at org.scalatest.SuperEngine$$anonfun$traverseSubNodes$1$1.apply(Engine.scala:413)
at org.scalatest.SuperEngine$$anonfun$traverseSubNodes$1$1.apply(Engine.scala:401)
at scala.collection.immutable.List.foreach(List.scala:381)
at org.scalatest.SuperEngine.traverseSubNodes$1(Engine.scala:401)
at org.scalatest.SuperEngine.org$scalatest$SuperEngine$$runTestsInBranch(Engine.scala:396)
at org.scalatest.SuperEngine.runTestsImpl(Engine.scala:483)
at org.scalatest.FunSuiteLike$class.runTests(FunSuiteLike.scala:208)
at org.scalatest.FunSuite.runTests(FunSuite.scala:1555)
at org.scalatest.Suite$class.run(Suite.scala:1424)
at org.scalatest.FunSuite.org$scalatest$FunSuiteLike$$super$run(FunSuite.scala:1555)
at org.scalatest.FunSuiteLike$$anonfun$run$1.apply(FunSuiteLike.scala:212)
at org.scalatest.FunSuiteLike$$anonfun$run$1.apply(FunSuiteLike.scala:212)
at org.scalatest.SuperEngine.runImpl(Engine.scala:545)
at org.scalatest.FunSuiteLike$class.run(FunSuiteLike.scala:212)
at org.scalatest.FunSuite.run(FunSuite.scala:1555)
at org.scalatest.tools.SuiteRunner.run(SuiteRunner.scala:55)
at org.scalatest.tools.Runner$$anonfun$doRunRunRunDaDoRunRun$3.apply(Runner.scala:2563)
at org.scalatest.tools.Runner$$anonfun$doRunRunRunDaDoRunRun$3.apply(Runner.scala:2557)
at scala.collection.immutable.List.foreach(List.scala:381)
at org.scalatest.tools.Runner$.doRunRunRunDaDoRunRun(Runner.scala:2557)
at org.scalatest.tools.Runner$$anonfun$runOptionallyWithPassFailReporter$2.apply(Runner.scala:1044)
at org.scalatest.tools.Runner$$anonfun$runOptionallyWithPassFailReporter$2.apply(Runner.scala:1043)
at org.scalatest.tools.Runner$.withClassLoaderAndDispatchReporter(Runner.scala:2722)
at org.scalatest.tools.Runner$.runOptionallyWithPassFailReporter(Runner.scala:1043)
at org.scalatest.tools.Runner$.run(Runner.scala:883)
at org.scalatest.tools.Runner.run(Runner.scala)
at org.jetbrains.plugins.scala.testingSupport.scalaTest.ScalaTestRunner.runScalaTest2(ScalaTestRunner.java:138)
at org.jetbrains.plugins.scala.testingSupport.scalaTest.ScalaTestRunner.main(ScalaTestRunner.java:28)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:140)
What is interesting, is that it will behave as expected if I make any of the following changes:
flatMap
with map
"A"
with ""
The problem is delayed evaluation from Stream
/Iterable
. This is very easy to see if you break it up into smaller pieces.
scala> Stream("A", "").toIterable.map(_.head)
res7: Iterable[Char] = Stream(A, ?)
Mapping the Stream
to _.head
means we're going to treat the strings as collections, and the empty string as a collection has no head
, so it will throw an exception. But it doesn't throw an exception right away, because an Iterable
is still lazy like Stream
, as you can see above. Only the first element as evaluated. If we make it non-lazy, it throws the exception:
scala> Stream("A", "").toIterable.map(_.head)
java.util.NoSuchElementException: next on empty iterator
Or, if you replace the "A"
with ""
, you will also get an exception, because the head of the Stream
is evaluated and mapped to head
, which has none.
scala> Stream("", "").toIterable.map(_.head).toList
java.util.NoSuchElementException: next on empty iterator
The only exception that will be caught in your code is when the head
of the Stream
or Iterable
throws it. Okay, so then why does map
not throw the exception, but flatMap
does? Well, this map
doesn't actually do anything to the Stream
:
scala> List(1).map { _ => Stream("A", "").toIterable.map(_.head) }
res11: List[Iterable[Char]] = List(Stream(A, ?))
So it is left untouched, with unevaluated members. But flatMap
will try to flatten the Iterable
s into one collection, so they must be evaluated to make a new collection. And in doing so, the exception is thrown:
scala> List(1).flatMap { _ => Stream("A", "").toIterable.map(_.head) }
java.util.NoSuchElementException: next on empty iterator
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With