I understand (kind of) how pattern matching works in Scala,
Let's say I have two lists of the form:
sealed abstract class IntList
case class Empty() extends IntList // The empty list, often called Nils
case class Element(n: Int, tail: IntList) extends IntList // Element is usually called Cons
Let's say I want to create the function take(n, xs)
It should return the first n elements of xs.
I tried with normal pattern matching:
def take(n: Int, xs: IntList): IntList = xs match {
case n == 0 => Empty()
case xs : Empty => Empty()
case xs : Element => Element(xs.n, take(n-1, xs))
}
But then of course, n is not recognised, error: not found: value == case n == 0 => Empty()
How can I do this, it is probably simple but I am a beginner in Scala?
You have a typo in the first case where it should be
case _ if n == 0 => Empty()
and a bug in third case where you forgot to pass the tail
case xs: Element => Element(xs.n, take(n-1, xs.tail))
Try
sealed trait IntList
case object Empty extends IntList
case class Element(n: Int, tail: IntList) extends IntList
def take(n: Int, xs: IntList): IntList = xs match {
case _ if n == 0 => Empty
case Empty => Empty
case Element(n, tail) => Element(n, take(n-1, tail))
}
val list = Element(1, Element(2, Element(3, Empty)))
take(2, list) // res0: IntList = Element(1,Element(2,Empty))
Consider case object instead of case class Empty() when there is no data as per Differences between case object T and case class T() when defining ADT?
Related
I've been playing with Dotty and tried to implement a simple List. This is how I've implemented it:
enum List[+A] {
case Cons(head :A, tail: List[A])
case Nil extends List[Nothing]
}
The issue I'm having is that this implementation fails to compile with Cannot rewrite recursive call: it is not in tail position:
#tailrec
def drop[A](n: Int, as: List[A]): List[A] =
(n,as) match
case (0, _) => as
case (_, Nil) => Nil
case (x, Cons(_, tail)) => drop(x-1, tail)
On a different file I tried the same implementation, with standard library's List, and it compiles:
#tailrec
def drop[A](n: Int, as: List[A]): List[A] =
(n,as) match
case (0, _) => as
case (_, Nil) => Nil
case (x, _ :: tail) => drop(x-1, tail)
Maybe I'm just tired and not seeing the obvious error, but maybe there's something else here? Any weirdness caused by that extends List[Nothing] I had to add to make the code compile?
Thanks!
EDIT:
My code that leads to the compilation error
In Dotty 0.25.0-bin-20200429-c5a76f0-NIGHTLY
import scala.annotation.tailrec
enum List[+A] {
case Cons(head :A, tail: List[A])
case Nil extends List[Nothing]
#tailrec
def drop[A](n: Int, as: List[A]): List[A] =
(n,as) match
case (0, _) => as
case (_, Nil) => Nil
case (x, Cons(_, tail)) => drop(x-1, tail)
}
produces
TailRec optimisation not applicable, method drop is neither private nor final so can be overridden
If you make drop final or private the code compiles.
Given the following list:
sealed abstract class IntList
case class Empty() extends IntList
case class Element(n: Int, tail: IntList) extends IntList
Define the function drop(n, xs).
It should return the list xs, without the first n elements.
This is what I tried:
def drop(n: Int, xs: IntList): IntList = xs match {
case _ if n == 0 => xs
case xs : Empty => Empty()
case xs : Element => Element(xs.tail.n, drop(n-1, xs.tail))
}
but
error: value n is not a member of Solution.IntList
case xs : Element => Element(xs.tail.n, drop(n-1, xs.tail))
I am guessing this is because xs.tail is not guaranteed to be an Element anymore
How should I do this? Thanks.
Nearly there, but there's no need to wrap the recursive call in Element()
def drop(n: Int, xs: IntList): IntList = xs match {
case _ if n == 0 => xs
case xs : Empty => xs
case xs : Element => drop(n-1, xs.tail)
}
This avoids the need to call xs.tail.n, which you already identified as the problem!
You can also just return xs for the Empty case, to avoid creating a new instance. Usually one would use a case object for an empty case like this.
Specifically:
scala> def f(n: Seq[Any]) = n match {
case Nil => "Empty"
case h :: t => "Non-empty"
}
f: (n: Seq[Any])String
scala> f(Stream())
res1: String = Empty
scala> f(List(1))
res17: String = Non-empty
scala> f(Stream(1))
scala.MatchError: Stream(1, ?) (of class scala.collection.immutable.Stream$Cons)
at .f(<console>:13)
... 33 elided
There are a lot of other ways to implement this, but at written the code was statically safe and failed at run time. What's going on?
For Stream, the concat symbol should be #::, the pattern match should like:
def f(n: Seq[Any]) = n match {
case Nil => "Empty"
case h :: t => "Non-empty"
case h #:: t => "Non-empty stream"
}
for :: is for List / Seq type(List extends from Seq:) ), see:
final case class ::[B](override val head: B, private[scala] var tl: List[B]) extends List[B] {
You can only use :: to deconstruct a List and not a Stream. Since the Stream you provide does not match a List, you get a MatchError. If you want f to support streams using that type of matching (extractors), you can use #::.
def f(n: Seq[Any]) = n match {
case Nil => "Empty"
case h :: t => "Non-empty"
case h #:: t => "Non-empty"
}
In general, this approach is very fragile because both extractor types shown above will only work for those two types of Seq. Others maybe break. If all you care about is determining whether or not the Seq is empty or not, then simply use n.nonEmpty or n.isEmpty and deal with the Boolean result. Otherwise, trying to provide an exhaustive match on a trait that is not sealed is bound to fail.
You can also use the Seq extractor:
def f(n: Seq[Any]) = n match {
case Nil => "Empty"
case Seq(_*) => "Non-empty"
}
While other answers show correctly an extractor specific for Stream, which is #::, there exists an extractor for Seq (and anything derived from SeqLike) - it is +:, this works for both List (as ::) and Stream (as `#::). With this extractor you can easily write a function which works with both:
def f(n: Seq[Any]) = n match {
case h +: t => "Non-empty"
case _ => "Empty"
}
See also Scala pattern matching on sequences other than Lists
I have a list of elements, and want to group them by class and then process the results.
trait H
case class A(x: Int) extends H
case class B(x: Int) extends H
val x = List(A(1),B(2),B(3))
val y = x.groupBy(_.getClass)
y.map(_ match {case (A, alist) => println("found me some As")
case (B, blist) => println("not As")})
Unfortunately this produces errors like:
<console>:17: error: pattern type is incompatible with expected type;
found : A.type
required: Class[_ <: Product]
Note: if you intended to match against the class, try `case _: A`
y.map(_ match {case (A, alist) => println("found me some As")
That is, I can't seem to find the proper way to do case matching when the item being matched is a class not just an instance of one.
A partial solution is the following:
val z = y.map(_ match {case (atype, alist) => alist })
z.map(_ match {
case alist if alist.head.isInstanceOf[A] => alist
case blist => List()
})
But it feels like there should be a better way of doing this using the keys to the initial Map returned from groupBy.
A in case (A, alist) refers to the companion object of A, which has the type A.type. That's why you're getting that error message. If you want to match against the class meta data, you should refer to classOf[A]. There's also no reason to use match, as you can pattern match with map already.
y.map {
case (c, alist) if(c == classOf[A]) => println("found me some As")
case (c, blist) if(c == classOf[B]) => println("not As")
}
Making my first steps in Scala, I have run into the first misunderstanding.
I took a classic example of a linked list.
sealed trait List[+A] // `List` data type, parameterized on a type, `A`
case object Nil extends List[Nothing] // A `List` data constructor representing the empty list
/* Another data constructor, representing nonempty lists. Note that `tail` is another `List[A]`,
which may be `Nil` or another `Cons`.
*/
case class Cons[+A](head: A, tail: List[A]) extends List[A]
object List { // `List` companion object. Contains functions for creating and working with lists.
def sum(ints: List[Int]): Int = ints match { // A function that uses pattern matching to add up a list of integers
case Nil => 0 // The sum of the empty list is 0.
case Cons(x,xs) => x + sum(xs) // The sum of a list starting with `x` is `x` plus the sum of the rest of the list.
}
def product(ds: List[Double]): Double = ds match {
case Nil => 1.0
case Cons(0.0, _) => 0.0
case Cons(x,xs) => x * product(xs)
}
def apply[A](as: A*): List[A] = // Variadic function syntax
if (as.isEmpty) Nil
else Cons(as.head, apply(as.tail: _*))
val x = List(1,2,3,4,5) match {
case Cons(x, Cons(2, Cons(4, _))) => x
case Nil => 42
case Cons(x, Cons(y, Cons(3, Cons(4, _)))) => x + y
case Cons(h, t) => h + sum(t)
case _ => 101
}
}
There are a few questions:
1) why custom linked List class doesn't conflict with built-in scala.collection.immutable.List.type
2) why a piece of code is supposed to be correct when we are matching built-in List to the custom linked list?
val x = List(1,2,3,4,5) match {
case Cons(x, Cons(2, Cons(4, _))) => x
case Nil => 42
case Cons(x, Cons(y, Cons(3, Cons(4, _)))) => x + y
case Cons(h, t) => h + sum(t)
case _ => 101
}
The custom linked-list class doesn't conflicts with the built-in scala.collection.immutable.List.type because local declarations, such as your custom List type, has higher precedence than an import (even non-explicit ones such as Scala's built-in List). See Chapter 2 of the Scala Specification for the full precedence order.
The referred matching code is not matching the built-in List, but your own locally declared List. You can see it yourself, by renaming your List to something like CustomList and see that some errors will appear, or to fully qualify the built-in List as the following code.
The following code actually matches the built-in List with your custom List structures and won't compile:
val x = scala.collection.immutable.List(1,2,3,4,5) match {
case Cons(x, Cons(2, Cons(4, _))) => x
case Nil => 42
case Cons(x, Cons(y, Cons(3, Cons(4, _)))) => x + y
case Cons(h, t) => h + sum(t)
case _ => 101
}
Your question is really about scope, I believe. You have defined your own List which is unrelated to that in scala.collection.immutable... The same with Cons and Nil.
When you instantiate the List in part 2), you are instantiating your List, not the one in the Scala library.
Or am I missing something?