7
votes

I would like to pattern match on different segments of a list in scala on the types of the head and tail:

class Solution07 extends FlatSpec with ShouldMatchers {
  "plain recursive flatten" should "flatten a list" in {
    val list1 = List(List(1, 1), 2, List(3, List(5, 8)))
    val list1Flattened = List(1, 1, 2, 3, 5, 8)

    flattenRecur(list1) should be (list1Flattened)
  }

  def flattenRecur(ls: List[Any]): List[Int] = ls match {
    case (head: Int) :: (tail: List[Any]) => head :: flattenRecur(tail)
    case (head: List[Int]) :: (tail: List[Any]) => head.head :: flattenRecur(head.tail :: tail)
    case (head: List[Any]) :: (tail: List[Any]) => flattenRecur(head) :: flattenRecur(tail) // non-variable type... on this line.
  }
}

I get:

Error:(18, 17) non-variable type argument Int in type pattern List[Int] (the underlying of List[Int]) is unchecked since it is eliminated by erasure case (head: List[Int]) :: (tail: List[Any]) => head.head :: flattenRecur(head.tail :: tail) ^

What am I missing? how is it possible for me to pattern match on the head and tail's types of the list?

2
try replace case (head: List[Int]) :: (tail: List[Any]) => with case ( (headhead : Int) :: (headtail : List[Any]) ) :: (tail : List[Any]) => That would allow you to fight type erasureayvango
The issue is for line case (head: List[Any]) :: (tail: List[Any]) you have mentioned a different line, is that supposed to help that line as well???Jas
the third choice conflicts with the second. So you could adjust either the second choice or the third choice. Since the third choice is more general, it's easier to made the second more specificayvango
are you claiming that I don't need the third case: case (head: List[Any]) :: (tail: List[Any]) because its conflicting?Jas
I suppose that all choices are needed for some of your functionality. You should not discard either.The issue is with scala's type erasure, that could not distinct the second and the third choice. So you should expand the second choice more to make types distinctive. For scala pattern matching all types List[blabla] is the same. But you could get head of List and specify type for it. And that type would be checked correctly. In the second choice the head is of type List[Int] that lacks description. So you should expand head further to headhead :: headtail. Now scala would catch typesayvango

2 Answers

11
votes

I agree that @Andreas solution with HList is a nice example for solving the problem, but I still do not understand what's the problem with this:

 def flatten(ls: List[_]): List[Int] = ls match {
    case Nil => Nil
    case (a: Int) :: tail => a :: flatten(tail)
    case (a: List[_]) :: tail => flatten(a) ::: flatten(tail)
    case _ :: tail => flatten(tail)
  }

Then:

println(flatten(List(List("one",9,8),3,"str",4,List(true,77,3.2)))) // List(9, 8, 3, 4, 77)

I don't see any problems with type erasure in your task, because you actually don't need to test the types that are erased. I've intentionally skipped all erased types in my example - to show this. Type erasure does not clear the type information of the elements of the list, it clears only the type information of list generic which you have Any or _ in my case - so you do not need that at all. If I am not missing someting, in your example the types are not erased at all, because you anyway have Any almost everywhere.

1
votes

You are hit by restrictions given by the object system:

The only common parent for List[Int] and Int is Any

So due to that the inference system can only safely assume that you could return Any. The solution proposed @jwvh is doable but has a possible danger of runtime exceptions.

If you want to solve the problem in a typesafe way an option could be to use HList of the shapeless library https://github.com/milessabin/shapeless : https://github.com/milessabin/shapeless/wiki/Feature-overview:-shapeless-2.0.0#heterogenous-lists