20
val x = for(i <- 1 to 3) yield i
x match {
    case 1 :: rest => ... // compile error
}

constructor cannot be instantiated to expected type; found : collection.immutable.::[B] required: scala.collection.immutable.IndexedSeq[Int]

This is the same problem as MatchError when match receives an IndexedSeq but not a LinearSeq.

The question is, how to do it right? Adding .toList everywhere doesn't seem right. And creating an own extractor which handles every Seq (as described in the answer of the other question) would lead to a mess if everybody would do it...

I guess the question is, why can't I influence what the return type of sequence comprehensions is, or: why isn't such a generalized Seq extractor part of the standard library?

Community
  • 1
  • 1
letmaik
  • 3,348
  • 1
  • 36
  • 43
  • 5
    This generalized extractor is available in 2.10: `val a +: b = 0 until 10` works like `val a :: b = (0 until 10).toList` does in 2.9. – Travis Brown Jul 16 '12 at 11:16
  • Hmm, very interesting! Although I'm not sure if it will also create a bit of mess having two extractors for lists. Is this extractor then supposed to replace ::? After all, it can do the same thing and is more generic. – letmaik Jul 16 '12 at 11:28
  • 1
    @TravisBrown that's good to know. Is there an equivalent of List's `Nil` as well? – Luigi Plinge Jul 16 '12 at 15:38
  • @Luigi: I don't think so—possibly because the `Seq(a, b, c)` syntax would be clearer in most situations where you want to match on `Nil`. – Travis Brown Jul 16 '12 at 19:40
  • 1
    @TravisBrown Good point. To check for the empty sequence, it would just be `Seq()`. Although according to this answer: http://stackoverflow.com/a/7602498/770361, `case List() => ` was a lot slower than `case Nil => ` before a dubious hack was put in to convert `List()` to `Nil`. So I hope that some way is found to ensure good performance. – Luigi Plinge Jul 16 '12 at 19:59

2 Answers2

35

Well, you can pattern-match any sequence:

case Seq(a, b, rest @ _ *) =>

For example:

scala> def mtch(s: Seq[Int]) = s match { 
  |      case Seq(a, b, rest @ _ *) => println("Found " + a + " and " + b)
  |      case _ => println("Bah") 
  |    }
mtch: (s: Seq[Int])Unit

Then this will match any sequence with more than (or equal to) 2 elements

scala> mtch(List(1, 2, 3, 4))
Found 1 and 2

scala> mtch(Seq(1, 2, 3))
Found 1 and 2

scala> mtch(Vector(1, 2))
Found 1 and 2

scala> mtch(Vector(1))
Bah
oxbow_lakes
  • 133,303
  • 56
  • 317
  • 449
  • I get scala.MatchError: Vector(1, 2, 3) (of class scala.collection.immutable.Vector) with that. Isn't it possible to only match case classes like that? – letmaik Jul 16 '12 at 11:25
  • It's possible to match anything with an extractor - in this case, anything with an `unapplySeq` method. – oxbow_lakes Jul 16 '12 at 12:01
  • Somewhere I read that instead of using `List` in method signatures it's better to use `Seq` if that also describes the problem. So if I have Seq everywhere, should I then always use Seq(..) pattern matching as the idiomatic way? Because with Seq I probably shouldn't match on List or :: anyway. Or wait for 2.10 and use +: everywhere? – letmaik Jul 16 '12 at 13:02
  • 1
    Well, 2.10 is several months away. I would usually use `List` myself, unless I am converting some huge data structure, in which case pattern-matching might not be the best bet anyway. If you are just testing for emptiness, you can always use `headOption` – oxbow_lakes Jul 16 '12 at 15:09
  • 1
    What I actually do now is matching `Seq()` for empty seqs, `Seq(a,b)` for fixed numbers of elements and `a +: rest` for head tail matching. I actually copied the relevant [extractors](https://github.com/jsuereth/scala/commit/b3efb3d493605d1c7e106e5f0a697b52ebb3d97c) into my project and created package objects with `+:` and `:+` for each package so that I don't have to import anything and can just delete those once 2.10 comes out. – letmaik Jul 17 '12 at 20:39
0

One more solution for Vector in REPL:

Vector() match {
    case a +: as => "head + tail"
    case _       => "empty"  
}
 res0: String = "empty"

Vector(1,2) match {
  case a +: as => s"$a + $as"
  case _      => "empty"  }
res1: String = 1 + Vector(2)
Roman Kazanovskyi
  • 3,370
  • 1
  • 21
  • 22