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")
}
Related
Was playing with Lazy Structure Stream as below
import Stream._
sealed trait Stream[+A] {
..
def toList: List[A] = this match {
case Empty => Nil
case Cons(h, t) => println(s"${h()}::t().toList"); h()::t().toList
}
def foldRight[B](z: B) (f: ( A, => B) => B) : B = this match {
case Empty => println(s"foldRight of Empty return $z"); z
case Cons(h, t) => println(s"f(${h()}, t().foldRight(z)(f))"); f(h(), t().foldRight(z)(f))
}
..
}
case object Empty extends Stream[Nothing]
case class Cons[+A](h: () => A, t: () => Stream[A]) extends Stream[A]
object Stream {
def cons[A](h: => A, t: => Stream[A]): Stream[A] = {
lazy val hd = h
lazy val tl = t
Cons[A](() => hd, () => tl)
}
def empty[A]: Stream[A] = Empty
def apply[A](la: A*): Stream[A] = la match {
case list if list.isEmpty => empty[A]
case _ => cons(la.head, apply(la.tail:_*))
}
}
For a function takeWhile via foldRight i initially wrote:
def takeWhileFoldRight_0(p: A => Boolean) : Stream[A] = {
foldRight(empty[A]) {
case (a, b) if p(a) => println(s"takeWhileFoldRight cons($a, b) with p(a) returns: cons($a, b)"); cons(a, b)
case (a, b) if !p(a) => println(s"takeWhileFoldRight cons($a, b) with !p(a) returns: empty[A]"); empty[A]
}
}
Which when called as:
Stream(4,5,6).takeWhileFoldRight_0(_%2 == 0).toList
result in the following trace:
f(4, t().foldRight(z)(f))
f(5, t().foldRight(z)(f))
f(6, t().foldRight(z)(f))
foldRight of Empty return Empty
takeWhileFoldRight cons(6, b) with p(a) returns: cons(6, b)
takeWhileFoldRight cons(5, b) with !p(a) returns: empty[A]
takeWhileFoldRight cons(4, b) with p(a) returns: cons(4, b)
4::t().toList
res2: List[Int] = List(4)
Then questioning and questioning i figured that it might have been the unapply method in the pattern match that evaluate eagerly.
So i changed to
def takeWhileFoldRight(p: A => Boolean) : Stream[A] = {
foldRight(empty[A]) { (a, b) =>
if (p(a)) cons(a, b) else empty[A]
}
}
which when called as
Stream(4,5,6).takeWhileFoldRight(_%2 == 0).toList
result in the following trace:
f(4, t().foldRight(z)(f))
4::t().toList
f(5, t().foldRight(z)(f))
res1: List[Int] = List(4)
Hence my question:
Is there a way to recover the power of pattern match when working with by-name parameter ?
Said differently case i match parameter that are by-name without evaluating them eagerly ?
Or i have to go to a set of ugly nested "if" :p in that kind of scenario
Take a closer look at this fragment:
def toList: List[A] = this match {
case Empty => Nil
case Cons(h, t) => println(s"${h()}::t().toList"); h()::t().toList
}
def foldRight[B](z: B) (f: ( A, => B) => B) : B = this match {
case Empty => println(s"foldRight of Empty return $z"); z
case Cons(h, t) => println(s"f(${h()}, t().foldRight(z)(f))"); f(h(), t().foldRight(z)(f))
}
..
}
Here h and t in Cons aren't evaluated by unapply - after all unapply returns () => X functions without calling them. But you do. Twice for each match - once for printing and once for passing the result on. And you aren't remembering the result, so any future fold, map, etc would evaluate the function anew.
Depending on what behavior you want to have you should either:
Calculate the results once, right after matching them:
case Cons(h, t) =>
val hResult = h()
val tResult = t()
println(s"${hResult}::tail.toList")
hResult :: tResult.toList
or
not use case class because it cannot memoize the result and you might need to memoize it:
class Cons[A](fHead: () => A, fTail: () => Stream[A]) extends Stream[A] {
lazy val head: A = fHead()
lazy val tail: Stream[A] = fTail()
// also override: toString, equals, hashCode, ...
}
object Cons {
def apply[A](head: => A, tail: => Stream[A]): Stream[A] =
new Cons(() => head, () => tail)
def unapply[A](stream: Stream[A]): Option[(A, Stream[A])] = stream match {
case cons: Cons[A] => Some((cons.head, cons.tail)) // matches on type, doesn't use unapply
case _ => None
}
}
If you understand what you're doing you could also create a case class with overridden apply and unapply (like above) but that is almost always a signal that you shouldn't use a case class in the first place (because most likely toString, equals, hashCode, etc would have nonsensical implementation).
Defining the following structure:
sealed trait List[+A]
case object Nil extends List[Nothing]
case class Cons[A](head: A, tail: List[A]) extends List[A]
and the following function (please note that the second line which is the type check error spot is also an algorithmic mistake for short-circuiting the result, but nonetheless it came up and i wonder why type checking is not spotting it)
def foldRight[A, B] (list: List[A], z: B)(f: (A,B) => B ): B = list match {
case Nil => z
case Cons(0, _) => z // problematic line
case Cons(s, xs) => f(s, foldRight(xs, z) (f))
}
I know scala also suffer from Type Erasure, but i am surprised that in this case, this can't be detected at compilation time ? Cons is Cons[A] in this case, and z is clearly of type B ?
Any reason why such code actually compile ?
EDIT1
Sounds like there is a lengthy explanation here https://gist.github.com/jkpl/5279ee05cca8cc1ec452fc26ace5b68b, but Long read. If someone could make it simpler :)
Why these or those decisions about language design were made is opinion based.
In Scala spec it's written:
https://scala-lang.org/files/archive/spec/2.13/08-pattern-matching.html#type-parameter-inference-for-constructor-patterns
8.3.2 Type parameter inference for constructor patterns
Assume a constructor pattern 𝐶(𝑝1,…,𝑝𝑛) where class 𝐶 has type
parameters 𝑎1,…,𝑎𝑛. These type parameters are inferred in the same
way as for the typed pattern (_: 𝐶[𝑎1,…,𝑎𝑛]).
So for pattern Cons(h, t) type parameter A in Cons[A](h, t) is inferred as if the pattern were _: Cons[A] (which becomes _: Cons[_] at runtime because of erasure).
There is example there in the spec how types are inferred for
class Term[A]
class Number(val n: Int) extends Term[Int]
def f[B](t: Term[B]): B = t match {
case y: Number => y.n
}
It's explained there why for the pattern y: Number type parameter B (new type parameter B) is inferred Int.
Similarly, in our case for the pattern Cons(0, _) (as if it were _: Cons[A]) A is inferred Int.
After typer phase (scalacOptions ++= Seq("-Xprint:typer", "-Xprint-types")) the code becomes
def foldRight[A, B](list: App.List[A], z: B)(f: (A, B) => B): B = list{App.List[A]} match {
case App.this{App.type}.Nil{App.Nil.type} => z{B}
case (head: A, tail: App.List[A]): App.Cons[?A1](0{Int(0)}, _{App.List[A]}){App.Cons[?A1]} => z{B}
case (head: A, tail: App.List[A]): App.Cons[?A2]((s # _{A}){A}, (xs # _{App.List[A]}){App.List[A]}){App.Cons[?A2]} => f.apply{(v1: A, v2: B): B}(s{A}, App.this{App.type}.foldRight{[A, B](list: App.List[A], z: B)(f: (A, B) => B): B}[A, B]{(list: App.List[A], z: B)(f: (A, B) => B): B}(xs{App.List[A]}, z{B}){(f: (A, B) => B): B}(f{(A, B) => B}){B}){B}
}{B}
and after erasure (scalacOptions += "-Xprint:erasure") it becomes
def foldRight(list: App$List, z: Object, f: Function2): Object = {
<synthetic> var rc9: Boolean = false;
<synthetic> <stable> var x2: App$Cons = (null: App$Cons);
{
case <synthetic> val x1: App$List = list;
case11(){
if (App$Nil.==(x1))
matchEnd10(z)
else
case12()
};
case12(){
if (x1.$isInstanceOf[App$Cons]())
{
rc9 = true;
x2 = (x1.$asInstanceOf[App$Cons](): App$Cons);
{
<synthetic> val p3: Object = x2.head();
if (scala.Int.box(0).==(p3))
matchEnd10(z)
else
case13()
}
}
else
case13()
};
case13(){
if (rc9)
{
val s: Object = x2.head();
val xs: App$List = x2.tail();
matchEnd10(f.apply(s, App.this.foldRight(xs, z, f)))
}
else
case14()
};
case14(){
matchEnd10(throw new MatchError(x1))
};
matchEnd10(x: Object){
x
}
}
};
Actually, code similar to yours compiles even in Haskell if we add the information that a type parameter a belongs to type classes Eq and Num to the context of function signature
https://ideone.com/qqOsI2
data List a = Nil | Cons a (List a)
foldRight :: (Eq a, Num a) => List a -> b -> (a -> b -> b) -> b
foldRight list z f = case list of
Nil -> z
Cons 0 _ -> z
Cons s xs -> f s (foldRight xs z f)
Type classes are not first-class citizens in Scala. So Scala can't request similar thing about something like Num (in Scala everything can be compared with ==, so Eq is "automatical").
Cons is Cons[A] in this case
This is not correct, or at very least it is confusing. There are two types A here: the type parameter in Cons[A] and the type parameter in foldRight[A,B]. There is nothing to say that they are the same type. You could equally well define
case class Cons[Z](head: Z, tail: List[Z]) extends List[Z]
So on the problematic line
case Cons(0, _) => z // problematic line
the compiler treats this as Cons[Int](0, _) because the type of head is Int. The compiler is promiscuous in this case and allows this match in all cases even though it can only succeed if foldLeft is called with a List[Int].
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?
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
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?