What is the idiomatic way to pattern match sequence comprehensions?
Asked Answered
C

2

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?

Corporeal answered 16/7, 2012 at 11:10 Comment(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.Syck
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.Corporeal
@TravisBrown that's good to know. Is there an equivalent of List's Nil as well?Coworker
@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.Syck
@TravisBrown Good point. To check for the empty sequence, it would just be Seq(). Although according to this answer: https://mcmap.net/q/663020/-nil-and-list-as-case-expressions-in-scala, 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.Coworker
G
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
Gambado answered 16/7, 2012 at 11:20 Comment(5)
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?Corporeal
It's possible to match anything with an extractor - in this case, anything with an unapplySeq method.Gambado
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?Corporeal
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 headOptionGambado
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 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.Corporeal
T
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)
Thermoscope answered 22/11, 2018 at 15:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.