61

I have the following code which recursively operates on each element within a List

def doMatch(list: List[Int]): Unit = list match {
  case last :: Nil  => println("Final element.")
  case head :: tail => println("Recursing..."); doMatch(tail)
}

Now, ignoring that this functionality is available through filter() and foreach(), this works just fine. However, if I try to change it to accept any Seq[Int], I run into problems:

  • Seq doesn't have ::, but it does have +:, which as I understand is basically the same thing. If I try to match on head +: tail however, the compiler complains 'error: not found: value +:'
  • Nil is specific to List, and I'm not sure what to replace it with. I'm going to try Seq() if I ever get past the previous problem

Here is how I think the code should look, except it doesn't work:

def doMatch(seq: Seq[Int]): Unit = seq match {
  case last +: Seq() => println("Final element.")
  case head +: tail  => println("Recursing..."); doMatch(tail)
}

Edit: So many good answers! I'm accepting agilesteel's answer as his was the first that noted that :: isn't an operator in my example, but a case class and hence the difference.

Zecrates
  • 2,952
  • 6
  • 33
  • 50
  • Two sidenotes: `final` is not allowed as an identifier there, and the compiler complains, that the cases aren't exhaustive. You could use: `def doMatch (list: List[Int]): Unit = list match { | case last :: Nil => println ("Final element.") case head :: tail => println ("Recursing..."); doMatch (tail) case Nil => println ("only seen for empty lists") }` instead. – user unknown Jul 24 '11 at 15:44
  • Yeah, the _final_ was a 'simplifying the scenario' error. I changed it to _last_ so that it will compile, but I left out your _case Nil_ so that the comment will make sense with the question. Thanks. – Zecrates Jul 25 '11 at 09:49
  • I can delete the comment - no problem. It would save new readers some time, to find a more correct question without comment, instead of something, that they like to correct, until they find a comment and an answer to the comment ... SE should be like a wiki, where people contribute to the solution - the documentation of the production isn't that important, and who wrote what. – user unknown Jul 25 '11 at 14:09
  • Why not use something similar to the `trycatch` method in [this SO question](http://stackoverflow.com/q/32917373/243233)? Essentially, use the size of the `Seq` and use `head` and `tail` to access the desired elements. – Jus12 Nov 02 '15 at 13:54

6 Answers6

61

As of the ides of March 2012, this works in 2.10+:

  def doMatch(seq: Seq[Int]): Unit = seq match {
    case last +: Seq() => println("Final element.")
    case head +: tail  => println("Recursing..."); doMatch(tail)
  }                                               //> doMatch: (seq: Seq[Int])Unit

  doMatch(List(1, 2))                             //> Recursing...
                                                  //| Final element.

More generally, two different head/tail and init/last decomposition objects mirroring append/prepend were added for Seq in SeqExtractors:

List(1, 2) match { case init :+ last => last } //> res0: Int = 2                                              
List(1, 2) match { case head +: tail => tail } //> res1: List[Int] = List(2)                                               
Vector(1, 2) match { case init :+ last => last } //> res2: Int = 2                                              
Vector(1, 2) match { case head +: tail => tail } //> res3: scala.collection.immutable.Vector[Int] = Vector(2)
yakshaver
  • 2,472
  • 1
  • 18
  • 21
56

Kind of cheating, but here it goes:

def doMatch(seq: Seq[Int]): Unit = seq match {
  case Seq(x) => println("Final element " + x)
  case Seq(x, xs@_*) => println("Recursing..." + x); doMatch(xs)
}

Don't ask me why xs* doesn't work...

Landei
  • 54,104
  • 13
  • 100
  • 195
  • 1
    I was wondering whether something like this would work, but I couldn't quite get the syntax to work. Do you consider it cheating in regards to my question, or cheating in a "generally you should not do this" sense? – Zecrates Jul 25 '11 at 09:46
  • 3
    @Zecrates: `_*` means "ignore the following elements of the pattern". With the `@` syntax I'm giving the "ignored" part a name, which feels like cheating. I don't think there is actually anything *wrong* with this code (of course `head` and `tail` might be inefficient on a given `Seq`). – Landei Jul 25 '11 at 12:10
  • 1
    You can also do this like so (if you know your Seq isn't empty: `val Seq(x, xs@_*) = seq` – Jaap Jan 17 '14 at 12:20
  • @Jaap If you do that, you'll fall into infinite recursion. – Dmitry Bespalov Feb 18 '15 at 06:40
  • 1
    yakshaver's answer is more up-to-date: https://stackoverflow.com/a/19147469/707926 – ps_ttf May 07 '18 at 15:44
29

There are two :: (pronounced cons) in Scala. One is an operator defined in class List and one is a class (subclass of List), which represents a non empty list characterized by a head and a tail.

head :: tail is a constructor pattern, which is syntactically modified from ::(head, tail).

:: is a case class, which means there is an extractor object defined for it.

agilesteel
  • 16,775
  • 6
  • 44
  • 55
  • I see, that is quite tricky! So, am I correct in saying you cannot pattern match on just any statement, it has to be a standalone object (or whatever we can call it)? I just tested and this seems the case, as I can pattern match on 2 but not on (1 + 1). – Zecrates Jul 24 '11 at 15:33
  • 1
    In generall, you are not matching on statements, you are matching against patterns. Extractors are the syntactical construct in Scala, which helps to define patterns. These are normally constructor patterns. When pattern matching on 2, the pattern is a constant, which is a completely different kind of pattern. 1 + 1 is an arbitrary expression, which does not conform to any kind of patterns, although it might look like one. – agilesteel Jul 24 '11 at 15:46
  • It's noteworthy that the cons :: operator will NOT work on Vector though it will work on Seq and and List. It's safer therefore to use +: – Coder Guy Aug 18 '17 at 19:13
27

You can actually define an object for +: to do exactly what you are looking for:

object +: { 
  def unapply[T](s: Seq[T]) = 
    if(s.nonEmpty)
      Some(s.head, s.tail) 
    else
      None
}

scala> val h +: t = Seq(1,2,3)
h: Int = 1
t: Seq[Int] = List(2, 3)

Then your code works exactly as expected.

This works because h +: t is equivalent to +:(h,t) when used for patten matching.

dhg
  • 52,383
  • 8
  • 123
  • 144
  • Note: s.size >= 1 can be slow on Seq and can be replaced with s.nonEmpty (which also seems more descriptive). – Suma Apr 09 '15 at 12:23
4

I don't think there is pattern matching support for arbitrary sequences in the standard library. You could do it with out pattern matching though:

  def doMatch(seq: Seq[Int]) {
    if (seq.size == 1) println("final element " + seq(0)) else {
      println("recursing")
      doMatch(seq.tail)
    }
  }
  doMatch(1 to 10)

You can define your own extractor objects though. See http://www.scala-lang.org/node/112

object SEQ {
  def unapply[A](s:Seq[A]):Option[(A, Seq[A])] = {
    if (s.size == 0) None else {
      Some((s.head, s.tail))
    }
  }
}

def doMatch(seq: Seq[Int]) {
  seq match {
    case SEQ(head, Seq()) => println("final")
    case SEQ(head, tail) => {
      println("recursing")
      doMatch(tail)
    }
  }
}
Kim Stebel
  • 41,826
  • 12
  • 125
  • 142
  • 1
    Does defining `EmptySeq()` buy you anything? Just using `Seq()` seems to work fine. – dhg Jul 24 '11 at 17:43
-2

A simple tranformation from Seq to List would do the job:

def doMatch (list: List[Int]): Unit = list match {           
    case last :: Nil => println ("Final element.")             
    case head :: tail => println ("Recursing..."); doMatch (tail)
    case Nil => println ("only seen for empty lists") 
  }

def doMatchSeq (seq: Seq[Int]) : Unit = doMatch (seq.toList)

doMatch (List(3, 4, 5))
doMatchSeq (3 to 5)
user unknown
  • 35,537
  • 11
  • 75
  • 121