jedesah
jedesah

Reputation: 3033

Scala Either unexpected type behavior

Why is this snippet not printing out: "You successfully implemented the function"

Details:

Why is the type of val actual seeming to be of type List[Either[List[Int], Int]], when I am even saying to the compiler explicitly that actual's type should be of type List[Int]?

// Flatten a nested list structure

def flatten[T](list: List[Either[List[T], T]]): List[T] = list flatMap {
    // TODO: Implement
    case list: List[T]  => list
    case element: T     => List(element)
}

implicit def ElementToEitherLeft[T](obj: T) = Left(obj)
implicit def ElementToEitherRight[T](obj: T) = Right(obj)

val list: List[Either[List[Int], Int]] = List(List(1, 1), 2, List(3, 5))
val actual: List[Int] = flatten[Int](list)
val expected = List(1, 1, 2, 3, 5)
if (actual == expected)     print("You successfully implemented the function")
else                        print("Unfortunatly, that's not quite rigth yet")

Upvotes: 1

Views: 248

Answers (2)

som-snytt
som-snytt

Reputation: 39587

If you compile with 2.10, you will see a warning like:

<console>:7: warning: match may not be exhaustive.
It would fail on the following inputs: Left(_), Right(_)

If you compile with 2.10-M6, you will also see a spurious warning like:

warning: unreachable code
case element: T     => List(element)

What you would like to see is a warning like:

warning: unreachable code
case list: List[T]  => list

so you could realize your misapprehension about what's going on in the function you're giving to flatMap, i.e. oh right it's a List[Either] not a List[List], but this is the compiler doing its best at the moment.

These are the warnings that are turned off under -Xno-patmat-analysis. There doesn't seem to be an option to turn it up to eleven.

You can eliminate your erasure warnings by getting rid of the type param, which is the natural thing to do, and then you get no support from the type system and no reachability or match warning:

def flatten(list: List[Either[List[_], _]]): List[Any] = list flatMap {
  case list: List[_]  => list
  case element        => List(element)
}

Upvotes: 1

Travis Brown
Travis Brown

Reputation: 139058

When you compiled your flatten you should have seen a warning like this:

warning: there were 2 unchecked warnings; re-run with -unchecked for details

If you'd then compiled with with -unchecked, you'd have seen this:

<console>:9: warning: non variable type-argument T in type pattern List[T] is unchecked since it is eliminated by erasure
           case list: List[T]  => list
                      ^
<console>:10: warning: abstract type T in type pattern T is unchecked since it is eliminated by erasure
           case element: T     => List(element)

In short, flatMap isn't going to unwrap your Either items for you, and what you've written only compiles because of some unpleasant facts about type erasure and pattern matching.

Fortunately there's an easy fix:

 def flatten[T](list: List[Either[List[T], T]]): List[T] = list flatMap {
   case Left(list)  => list
   case Right(item) => item :: Nil
 }

Or, even better:

def flatten[T](list: List[Either[List[T], T]]): List[T] =
  list.flatMap(_.fold(identity, List(_)))

Either will work as you expect.

Upvotes: 7

Related Questions