1

Just trying to complete an exercise for flattening nested Lists which can contain Ints, nested Lists, or null.

This is easy to do with List[Any] but I was wondering if it's possible to get compile-time checking on the inputs/test values.

So test case

flatten(List(0, 2, List(List(2, 3), 8, List(List(100)), null, List(List(null))), -2))
  should be(List(0, 2, 2, 3, 8, 100, -2))

But I'd want a test with (say) a String in it to fail compilation.

flatten(List(0, 2, List(List(2, 3), 8, List(List(100)), "null", List(List(null))), -2))
  // should not compile

So I wrote this

object FlattenArray:
  type NestedList[T] = Int | List[NestedList[_]] | Null

  def flatten(xs: NestedList[_]): List[Int] =
    xs match
      case (x: Int) :: tail         => x :: flatten(tail)
      case (ys: List[_]) :: tail    => flatten(ys) ::: flatten(tail)
      case null :: tail             => flatten(tail)
      case Nil                      => Nil
      case _                        => throw new IllegalArgumentException("This should never happen")

Which does work in the first and fail compilation as expected in the second test case, but rather than a graceful error message, I get a stack overflow:

[error] java.lang.StackOverflowError
[error] dotty.tools.dotc.core.Types$Type.dealias1(Types.scala:1369)
[error] dotty.tools.dotc.core.Types$Type.dealias(Types.scala:1387)
[error] dotty.tools.dotc.typer.ImplicitRunInfo$$anon$1.apply(Implicits.scala:744)
[error] dotty.tools.dotc.core.Types$TypeMap.op$proxy16$1(Types.scala:5246)
[error] dotty.tools.dotc.core.Types$TypeMap.mapArgs(Types.scala:5246)
[error] dotty.tools.dotc.core.Types$TypeMap.mapOver(Types.scala:5281)
[error] dotty.tools.dotc.typer.ImplicitRunInfo$$anon$1.apply(Implicits.scala:750)
[error] dotty.tools.dotc.core.Types$TypeMap.mapOver(Types.scala:5345)
[error] dotty.tools.dotc.typer.ImplicitRunInfo$$anon$1.apply(Implicits.scala:750)
[error] dotty.tools.dotc.core.Types$TypeMap.mapOver(Types.scala:5345)
[error] dotty.tools.dotc.typer.ImplicitRunInfo$$anon$1.apply(Implicits.scala:750)
<repeat last 8 lines ad infinitum>

So my questions are:

  1. Am I doing this right?? (Unsure in particular about the type parameter in the type declaration.) Is there a better way to achieve the compile time type check?
  2. Is this Scala 3's expected failure mode?
user
  • 7,435
  • 3
  • 14
  • 44
Luigi Plinge
  • 50,650
  • 20
  • 113
  • 180
  • In a [Scastie worksheet](https://scastie.scala-lang.org/9MmPDi8gRhWaGbpbJh7U0A) it compiles and runs and hits the `case _` condition when the `String` is encountered. – jwvh Aug 23 '21 at 03:01
  • Hmm... I was compiling with sbt and the test cases were in ScalaTest but this inconsistency with Scastie is strange. Anyway, that suggests my type is incorrect. Not sure what the correct way to achieve this is. – Luigi Plinge Aug 23 '21 at 10:59
  • 1
    This looks like a bug in the compiler. Are you sure you're using the latest stable version? (3.0.1 iirc) – user Aug 23 '21 at 11:33
  • @user I'm on scala 3.0.1 with sbt version 1.5.5 and Scalatest 3.2.9 – Luigi Plinge Aug 23 '21 at 11:53
  • Oh, interesting, I expected it to be a lower version if you got different behavior. (btw, should the union's first type be `List[Int]` instead of `Int`? – user Aug 23 '21 at 12:00
  • [Here's](https://scastie.scala-lang.org/cJrIVcFSSbufqpzvgPjfzQ) one solution, but I'm afraid it's not very nice. – user Aug 23 '21 at 12:10
  • You might be right about `List[Int]` rather than `Int`, although I think the original should work too, as a `List[NestedList[_]]` should cover `List[Int]` if `NestedList[T]` can be an `Int`... I tried compiling without the test class and putting a `println` in the `FlattenArray` object and that works as per Scastie, so maybe I'm running into something about the way Scalatest uses type inference. Thanks for your solution, but the test cases need to be plain old `List` without including my new type. – Luigi Plinge Aug 23 '21 at 12:26

0 Answers0