I have a situation where I have a couple of case classes where all of their variables are optional.
Let's say I have:
case class Size(width: Option[Int], height: Option[Int])
case class Foo(a: Option[String], b: Option[Boolean], c: Option[Char])
Given a collection of the same type of case class I would like to fold over them comparing the option values and keep the values which are defined. I.e. for Size:
values.foldLeft(x) { (a, b) =>
Size(a.width.orElse(b.width), a.height.orElse(b.height))
}
I would like to do this in a more general way for any of the case classes like the ones above. I'm thinking about doing something with unapply(_).get etc. Does anyone know a smart way to solve this?
Ok, consider this:
def foldCase[C,T1](unapply: C => Option[Option[T1]], apply: Option[T1] => C)
(coll: Seq[C]): C = {
coll.tail.foldLeft(coll.head) { case (current, next) =>
apply(unapply(current).get orElse unapply(next).get)
}
}
case class Person(name: Option[String])
foldCase(Person.unapply, Person.apply)(List(Person(None), Person(Some("Joe")), Person(Some("Mary"))))
One could overload foldCase to accept two, three, or more parameters, one version of f for each arity. It could then be used with any case class. Since there's the tuple-thing to worry about, below's one way to make it work with case classes or two parameters. Expanding it to more parameters is then trivial, though a bit tiresome.
def foldCase[C,T1,T2](unapply: C => Option[(Option[T1], Option[T2])], apply: (Option[T1], Option[T2]) => C)
(coll: Seq[C]): C = {
def thisOrElse(current: (Option[T1], Option[T2]), next: (Option[T1], Option[T2])) =
apply(current._1 orElse next._1, current._2 orElse next._2)
coll.tail.foldLeft(coll.head) { case (current, next) =>
thisOrElse(unapply(current).get, unapply(next).get)
}
}
val list = Person(None, None) :: Person(Some("Joe"), None) :: Person(None, Some(20)) :: Person(Some("Mary"), Some(25)) :: Nil
def foldPerson = foldCase(Person.unapply, Person.apply) _
foldPerson(list)
To use it overloaded, just put all definitions inside one object:
object Folder {
def foldCase[C,T1](unapply: C => Option[Option[T1]], apply: Option[T1] => C)
(coll: Seq[C]): C = {
coll.tail.foldLeft(coll.head) { case (current, next) =>
apply(unapply(current).get orElse unapply(next).get)
}
}
def foldCase[C,T1,T2](unapply: C => Option[(Option[T1], Option[T2])], apply: (Option[T1], Option[T2]) => C)
(coll: Seq[C]): C = {
def thisOrElse(current: (Option[T1], Option[T2]), next: (Option[T1], Option[T2])) =
apply(current._1 orElse next._1, current._2 orElse next._2)
coll.tail.foldLeft(coll.head) { case (current, next) =>
thisOrElse(unapply(current).get, unapply(next).get)
}
}
}
When you do this, however, you'll have to explicitly turn apply and unapply into functions:
case class Question(answer: Option[Boolean])
val list2 = List(Question(None), Question(Some(true)), Question(Some(false)))
Folder.foldCase(Question.unapply _, Question.apply _)(list2)
It might be possible to turn it into a structural type, so that you only need to pass the companion object, but I couldn't do it. On #scala, I was told the answer is a definitive no, at least to how I approached the problem.
[Code updated]
Here is an solution which requires only one abstract class per "arity":
abstract class Foldable2[A,B](val a:Option[A], val b:Option[B]) {
def orElse[F <: Foldable2[A,B]](that: F)(implicit ev: this.type <:< F) =
getClass.getConstructor(classOf[Option[A]], classOf[Option[B]]).newInstance(
this.a.orElse(that.a), this.b.orElse(that.b)
)
}
case class Size(w: Option[Int], h: Option[Int]) extends Foldable2(w, h)
println(Size(Some(1),None).orElse(Size(Some(2),Some(42))))
//--> Size(Some(1),Some(42))
Note that the implicit <:< argument will give a compile time error when other case classes with the same constructor arguments are passed to the method.
However, a "well formed" constructor is required, else the reflection code will blow up.
You can use productElement or productIterator (on scala.Product) to generically retrieve/iterate the elements of case classes (and tuples), but they're typed as Any, so there will be some pain.
Related
I often find myself needing to chain collects where I want to do multiple collects in a single traversal. I also would like to return a "remainder" for things that don't match any of the collects.
For example:
sealed trait Animal
case class Cat(name: String) extends Animal
case class Dog(name: String, age: Int) extends Animal
val animals: List[Animal] =
List(Cat("Bob"), Dog("Spot", 3), Cat("Sally"), Dog("Jim", 11))
// Normal way
val cats: List[Cat] = animals.collect { case c: Cat => c }
val dogAges: List[Int] = animals.collect { case Dog(_, age) => age }
val rem: List[Animal] = Nil // No easy way to create this without repeated code
This really isn't great, it requires multiple iterations and there is no reasonable way to calculate the remainder. I could write a very complicated fold to pull this off, but it would be really nasty.
Instead, I usually opt for mutation which is fairly similar to the logic you would have in a fold:
import scala.collection.mutable.ListBuffer
// Ugly, hide the mutation away
val (cats2, dogsAges2, rem2) = {
// Lose some benefits of type inference
val cs = ListBuffer[Cat]()
val da = ListBuffer[Int]()
val rem = ListBuffer[Animal]()
// Bad separation of concerns, I have to merge all of my functions
animals.foreach {
case c: Cat => cs += c
case Dog(_, age) => da += age
case other => rem += other
}
(cs.toList, da.toList, rem.toList)
}
I don't like this one bit, it has worse type inference and separation of concerns since I have to merge all of the various partial functions. It also requires lots of lines of code.
What I want, are some useful patterns, like a collect that returns the remainder (I grant that partitionMap new in 2.13 does this, but uglier). I also could use some form of pipe or map for operating on parts of tuples. Here are some made up utilities:
implicit class ListSyntax[A](xs: List[A]) {
import scala.collection.mutable.ListBuffer
// Collect and return remainder
// A specialized form of new 2.13 partitionMap
def collectR[B](pf: PartialFunction[A, B]): (List[B], List[A]) = {
val rem = new ListBuffer[A]()
val res = new ListBuffer[B]()
val f = pf.lift
for (elt <- xs) {
f(elt) match {
case Some(r) => res += r
case None => rem += elt
}
}
(res.toList, rem.toList)
}
}
implicit class Tuple2Syntax[A, B](x: Tuple2[A, B]){
def chainR[C](f: B => C): Tuple2[A, C] = x.copy(_2 = f(x._2))
}
Now, I can write this in a way that could be done in a single traversal (with a lazy datastructure) and yet follows functional, immutable practice:
// Relatively pretty, can imagine lazy forms using a single iteration
val (cats3, (dogAges3, rem3)) =
animals.collectR { case c: Cat => c }
.chainR(_.collectR { case Dog(_, age) => age })
My question is, are there patterns like this? It smells like the type of thing that would be in a library like Cats, FS2, or ZIO, but I am not sure what it might be called.
Scastie link of code examples: https://scastie.scala-lang.org/Egz78fnGR6KyqlUTNTv9DQ
I wanted to see just how "nasty" a fold() would be.
val (cats
,dogAges
,rem) = animals.foldRight((List.empty[Cat]
,List.empty[Int]
,List.empty[Animal])) {
case (c:Cat, (cs,ds,rs)) => (c::cs, ds, rs)
case (Dog(_,d),(cs,ds,rs)) => (cs, d::ds, rs)
case (r, (cs,ds,rs)) => (cs, ds, r::rs)
}
Eye of the beholder I suppose.
How about defining a couple utility classes to help you with this?
case class ListCollect[A](list: List[A]) {
def partialCollect[B](f: PartialFunction[A, B]): ChainCollect[List[B], A] = {
val (cs, rem) = list.partition(f.isDefinedAt)
new ChainCollect((cs.map(f), rem))
}
}
case class ChainCollect[A, B](tuple: (A, List[B])) {
def partialCollect[C](f: PartialFunction[B, C]): ChainCollect[(A, List[C]), B] = {
val (cs, rem) = tuple._2.partition(f.isDefinedAt)
ChainCollect(((tuple._1, cs.map(f)), rem))
}
}
ListCollect is just meant to start the chain, and ChainCollect takes the previous remainder (the second element of the tuple) and tries to apply a PartialFunction to it, creating a new ChainCollect object. I'm not particularly fond of the nested tuples this produces, but you may be able to make it look a bit better if you use Shapeless's HLists.
val ((cats, dogs), rem) = ListCollect(animals)
.partialCollect { case c: Cat => c }
.partialCollect { case Dog(_, age) => age }
.tuple
Scastie
Dotty's *: type makes this a bit easier:
opaque type ChainResult[Prev <: Tuple, Rem] = (Prev, List[Rem])
extension [P <: Tuple, R, N](chainRes: ChainResult[P, R]) {
def partialCollect(f: PartialFunction[R, N]): ChainResult[List[N] *: P, R] = {
val (cs, rem) = chainRes._2.partition(f.isDefinedAt)
(cs.map(f) *: chainRes._1, rem)
}
}
This does end up in the output being reversed, but it doesn't have that ugly nesting from my previous approach:
val ((owls, dogs, cats), rem) = (EmptyTuple, animals)
.partialCollect { case c: Cat => c }
.partialCollect { case Dog(_, age) => age }
.partialCollect { case Owl(wisdom) => wisdom }
/* more animals */
case class Owl(wisdom: Double) extends Animal
case class Fly(isAnimal: Boolean) extends Animal
val animals: List[Animal] =
List(Cat("Bob"), Dog("Spot", 3), Cat("Sally"), Dog("Jim", 11), Owl(200), Fly(false))
Scastie
And if you still don't like that, you can always define a few more helper methods to reverse the tuple, add the extension on a List without requiring an EmptyTuple to begin with, etc.
//Add this to the ChainResult extension
def end: Reverse[List[R] *: P] = {
def revHelp[A <: Tuple, R <: Tuple](acc: A, rest: R): RevHelp[A, R] =
rest match {
case EmptyTuple => acc.asInstanceOf[RevHelp[A, R]]
case h *: t => revHelp(h *: acc, t).asInstanceOf[RevHelp[A, R]]
}
revHelp(EmptyTuple, chainRes._2 *: chainRes._1)
}
//Helpful types for safety
type Reverse[T <: Tuple] = RevHelp[EmptyTuple, T]
type RevHelp[A <: Tuple, R <: Tuple] <: Tuple = R match {
case EmptyTuple => A
case h *: t => RevHelp[h *: A, t]
}
And now you can do this:
val (cats, dogs, owls, rem) = (EmptyTuple, animals)
.partialCollect { case c: Cat => c }
.partialCollect { case Dog(_, age) => age }
.partialCollect { case Owl(wisdom) => wisdom }
.end
Scastie
Since you mentioned cats, I would also add solution using foldMap:
sealed trait Animal
case class Cat(name: String) extends Animal
case class Dog(name: String) extends Animal
case class Snake(name: String) extends Animal
val animals: List[Animal] = List(Cat("Bob"), Dog("Spot"), Cat("Sally"), Dog("Jim"), Snake("Billy"))
val map = animals.foldMap{ //Map(other -> List(Snake(Billy)), cats -> List(Cat(Bob), Cat(Sally)), dogs -> List(Dog(Spot), Dog(Jim)))
case d: Dog => Map("dogs" -> List(d))
case c: Cat => Map("cats" -> List(c))
case o => Map("other" -> List(o))
}
val tuples = animals.foldMap{ //(List(Dog(Spot), Dog(Jim)),List(Cat(Bob), Cat(Sally)),List(Snake(Billy)))
case d: Dog => (List(d), Nil, Nil)
case c: Cat => (Nil, List(c), Nil)
case o => (Nil, Nil, List(o))
}
Arguably it's more succinct than fold version, but it has to combine partial results using monoids, so it won't be as performant.
This code is dividing a list into three sets, so the natural way to do this is to use partition twice:
val (cats, notCat) = animals.partitionMap{
case c: Cat => Left(c)
case x => Right(x)
}
val (dogAges, rem) = notCat.partitionMap {
case Dog(_, age) => Left(age)
case x => Right(x)
}
A helper method can simplify this
def partitionCollect[T, U](list: List[T])(pf: PartialFunction[T, U]): (List[U], List[T]) =
list.partitionMap {
case t if pf.isDefinedAt(t) => Left(pf(t))
case x => Right(x)
}
val (cats, notCat) = partitionCollect(animals) { case c: Cat => c }
val (dogAges, rem) = partitionCollect(notCat) { case Dog(_, age) => age }
This is clearly extensible to more categories, with the slight irritation of having to invent temporary variable names (which could be overcome by explicit n-way partition methods)
I am having problems understanding the Scala syntax, please advice. I have two snippets of code.
abstract class Try[T] {
def flatMap[U](f: T => Try[U]): Try[U] = this match {
case Success(x) => try f(x) catch { case NonFatal(ex) => Failure(ex) }
case fail: Failure => fail
}
}
My understanding:
flatMap received as parameter a function f. In turn this function f
receives type parameter T and returns Try of type parameter U.
flatMap ultimately return Try of type parameter U.
Q1 - Is my understanding correct?
Q2 - what is the relation between the return type from f (namely Try[U]) and the return type of flat map Try[U]? Does it have to be the same?
def flatMap[U](f: T => Try[U]): Try[U]
Or can I somehow have something like
def flatMap[U](f: T => Option[U]): Try[U]
In the last snippet of code, I guess that, after I use the function f inside my flatMap, I would need to make the connection between the output of f (namely Option[U]) and the final output demanded by flatMap (I mean Try[U])
EDIT
This code is taken from a scala course. here is the full code (some people asked about it). I just want to understand the syntax.
abstract class Try[T] {
def flatMap[U](f: T => Try[U]): Try[U] = this match {
case Success(x) => try f(x) catch { case NonFatal(ex) => Failure(ex) }
case fail: Failure => fail
}
def map[U](f: T => U): Try[U] = this match {
case Success(x) => Try(f(x))
case fail: Failure => fail
}
}
Q1 - Is my understanding correct?
It's hard to comment based on your sample code which has method implementation in an abstract class while no concrete classes are defined. Lets consider the following toy version of Try extracted from the Scala API with the flatMap implementation in its concrete classes:
import scala.util.control.NonFatal
sealed abstract class MyTry[+T] {
def flatMap[U](f: T => MyTry[U]): MyTry[U]
}
object MyTry {
def apply[T](r: => T): MyTry[T] =
try MySuccess(r) catch { case NonFatal(e) => MyFailure(e) }
}
final case class MyFailure[+T](exception: Throwable) extends MyTry[T] {
override def flatMap[U](f: T => MyTry[U]): MyTry[U] =
this.asInstanceOf[MyTry[U]]
}
final case class MySuccess[+T](value: T) extends MyTry[T] {
override def flatMap[U](f: T => MyTry[U]): MyTry[U] =
try f(value) catch { case NonFatal(e) => MyFailure(e) }
}
Testing it out with the following function f: T => MyTry[U] where T = String and U = Int, I hope it helps answer your question:
val f: String => MyTry[Int] = s => s match {
case "bad" => MyFailure(new Exception("oops"))
case s => MySuccess(s.length)
}
MyTry("abcde").flatMap(f)
// res1: MyTry[Int] = MySuccess(5)
MyTry("bad").flatMap(f)
// res2: MyTry[Int] = MyFailure(java.lang.Exception: oops)
Q2 - what is the relation between the return type from f (namely Try[U])
and the return type of flat map Try[U]? Does it have to be the same?
In Scala, flatMap is a common method defined in many of Scala containers/collections such as Option[T], List[T], Try[T], Future[T], with a standard signature:
class Container[T] {
def flatMap[U](f: T => Container[U]): Container[U]
}
If you want to have a special map that takes a T => Container1[U] function and returns a Container2[U], it'd probably best not to name it flatMap.
Q1 Largely correct, but just to clarify, all of this happens at compile time - T is not known at runtime (see here)
Q2 Of course you can create a method with signature
...[U](f: T => Option[U]): Try[U]
and you're free to call that method flatMap, but it won't be a standard flatMap:
trait T[A] {
flatMap[B](f: A => T[B]): T[B]
}
There are mathematical reasons for the form of flatMap (which also have implications in Scala's implementation of for expressions). To avoid confusion ...
Rather than altering flatMap's signature, wrap your T => Option[U] with an Option[U] => Try[U] to create a T => Try[U] before passing it to flatMap.
I tried to replace case class with mundane class and companion object and suddenly get type error.
Code that compiles fine (synthetic example):
trait Elem[A,B] {
def ::[C](other : Elem[C,A]) : Elem[C,B] = other match {
case Chain(head, tail) => Chain(head, tail :: this)
case simple => Chain(simple, this)
}
}
class Simple[A,B] extends Elem[A,B]
final case class Chain[A,B,C](head : Elem[A,B], tail : Elem[B,C]) extends Elem[A,C]
Change the last definition with:
final class Chain[A,B,C](val head : Elem[A,B], val tail : Elem[B,C]) extends Elem[A,C]
object Chain {
def unapply[A,B,C](src : Chain[A,B,C]) : Option[(Elem[A,B], Elem[B,C])] =
Some( (src.head, src.tail) )
def apply[A,B,C](head : Elem[A,B], tail : Elem[B,C]) : Chain[A,B,C] =
new Chain(head, tail)
}
But that seemingly equivalent code make compiler emit errors:
CaseMystery.scala:17: error: type mismatch;
found : test.casemystery.Fail.Elem[A,B] where type B, type A >: C <: C
required: test.casemystery.Fail.Elem[A,Any] where type A >: C <: C
Note: B <: Any, but trait Elem is invariant in type B.
You may wish to define B as +B instead. (SLS 4.5)
case Chain(head, tail) => Chain(head, tail :: this)
^
CaseMystery.scala:17: error: type mismatch;
found : test.casemystery.Fail.Elem[B(in method ::),B(in trait Elem)] where type B(in method ::)
required: test.casemystery.Fail.Elem[Any,B(in trait Elem)]
Note: B <: Any, but trait Elem is invariant in type A.
You may wish to define A as +A instead. (SLS 4.5)
case Chain(head, tail) => Chain(head, tail :: this)
^
two errors found
What is the difference between implicitly created method with the case statement and explicitly written methods for mundane class?
This answer ended up being longer than I expected. If you just want the guts of what is happening with type inference, skip to the end. Otherwise, you get led through the steps of getting to the answer.
The problem is in the case, but not the one in case class
In this case, as much as I hate to admit it, case classes really are magic. In particular, they get special treatment at the type checker level (I think we can agree that your code would work if it got past that phase - you might even be able to throw enough casts at it to make that work).
The problem is, surprisingly enough, not in the class Chain itself, but in the places it is used, specifically in the pattern matching part. For example, consider the case class
case class Clazz(field: Int)
Then, you expect the following to be equivalent:
Clazz(3) match { case Clazz(i) => i }
// vs
val v = Clazz.unapply(Clazz(3))
if (v.isDefined) v.get else throw new Exception("No match")
But, Scala wants to be more clever and optimize this. In particular, this unapply method pretty can pretty much never fail (let's ignore null for now) and is probably used a lot, so Scala wants to avoid it altogether and just extract the fields as it usually would get any member of an object. As my compiler professor is fond of saying, "compilers are the art of cheating without getting caught".
Yet here there is a difference in the type-checker. The problem is in
def ::[Z, X](other : Elem[Z, X]) : Elem[Z, Y] = other match {
case Chain(head, tail) => Chain(head, tail :: this)
case simple => Chain(simple, this)
}
If you compile with -Xprint:typer you'll see what the type checker sees. The case class version has
def ::[C](other: Elem[C,A]): Elem[C,B] = other match {
case (head: Elem[C,Any], tail: Elem[Any,A])Chain[C,Any,A]((head # _), (tail # _)) => Chain.apply[C, Any, B](head, {
<synthetic> <artifact> val x$1: Elem[Any,A] = tail;
this.::[Any](x$1)
})
case (simple # _) => Chain.apply[C, A, B](simple, this)
}
While the regular class has
def ::[C](other: Elem[C,A]): Elem[C,B] = other match {
case Chain.unapply[A, B, C](<unapply-selector>) <unapply> ((head # _), (tail # _)) => Chain.apply[A, Any, B](<head: error>, {
<synthetic> <artifact> val x$1: Elem[_, _ >: A <: A] = tail;
this.::[B](x$1)
})
case (simple # _) => Chain.apply[C, A, B](simple, this)
}
So the type checker actually gets a different (special) case construct.
So what does the match get translated to?
Just for fun, we can check what happens at the next phase -Xprint:patmat which expands out patterns (although here the fact that these are no longer really valid Scala programs really becomes painful). First, the case class has
def ::[C](other: Elem[C,A]): Elem[C,B] = {
case <synthetic> val x1: Elem[C,A] = other;
case5(){
if (x1.isInstanceOf[Chain[C,Any,A]])
{
<synthetic> val x2: Chain[C,Any,A] = (x1.asInstanceOf[Chain[C,Any,A]]: Chain[C,Any,A]);
{
val head: Elem[C,Any] = x2.head;
val tail: Elem[Any,A] = x2.tail;
matchEnd4(Chain.apply[C, Any, B](head, {
<synthetic> <artifact> val x$1: Elem[Any,A] = tail;
this.::[Any](x$1)
}))
}
}
else
case6()
};
case6(){
matchEnd4(Chain.apply[C, A, B](x1, this))
};
matchEnd4(x: Elem[C,B]){
x
}
}
Although a lot of stuff is confusing here, notice that we never use the unapply method! For the non-case class version, I'll use the working code from user1303559:
def ::[Z, XX >: X](other: Elem[Z,XX]): Elem[Z,Y] = {
case <synthetic> val x1: Elem[Z,XX] = other;
case6(){
if (x1.isInstanceOf[Chain[A,B,C]])
{
<synthetic> val x2: Chain[A,B,C] = (x1.asInstanceOf[Chain[A,B,C]]: Chain[A,B,C]);
{
<synthetic> val o8: Option[(Elem[A,B], Elem[B,C])] = Chain.unapply[A, B, C](x2);
if (o8.isEmpty.unary_!)
{
val head: Elem[Z,Any] = o8.get._1;
val tail: Elem[Any,XX] = o8.get._2;
matchEnd5(Chain.apply[Z, Any, Y](head, {
<synthetic> <artifact> val x$1: Elem[Any,XX] = tail;
this.::[Any, XX](x$1)
}))
}
else
case7()
}
}
else
case7()
};
case7(){
matchEnd5(Chain.apply[Z, XX, Y](x1, this))
};
matchEnd5(x: Elem[Z,Y]){
x
}
}
And here, sure enough, the unapply method makes an appearance.
It isn't actually cheating (for the Pros)
Of course, Scala doesn't actually cheat - this behavior is all in the specification. In particular, we see that constructor patterns from which case classes benefit are kind of special, since, amongst other things, they are irrefutable (related to what I was saying above about Scala not wanting to use the unapply method since it "knows" it is just extracting the fields).
The part that really interests us though is 8.3.2 Type parameter inference for constructor patterns. The difference between the regular class and the case class is that Chain pattern is a "constructor pattern" when Chain is a case class, and just a regular pattern otherwise. The constructor pattern
other match {
case Chain(head, tail) => Chain(head, tail :: this)
case simple => Chain(simple, this)
}
ends up getting typed as though it were
other match {
case _: Chain[a1,a2,a3] => ...
}
Then, based on the fact that other: Elem[C,A] from the argument types and the fact that Chain[a1,a2,a3] extends Elem[a1,a3], we get that a1 is C, a3 is A and a2 can by anything, so is Any. Hence why the types in the output of -Xprint:typer for the case class has an Chain[C,Any,A] in it. This does type check.
However, constructor patterns are specific to case classes, so no - there is no way to imitate the case class behavior here.
A constructor pattern is of the form c(p1,…,pn) where n≥0. It
consists of a stable identifier c, followed by element patterns
p1,…,pn. The constructor c is a simple or qualified name which
denotes a case class.
Firstly other is Elem[C, A], but after you had tried to match it as Chain(head, tail) it actually matched to Chain[C, some inner B, A](head: Elem[C, inner B], tail: Elem[inner B, A]). After that you create Chain[C, inner B <: Any, A](head: Elem[C, inner B], (tail :: this): Elem[inner B, B])
But result type must be Elem[C, B], or Chain[C, Any, B]. So compiler trying to cast inner B to Any. But beacause inner B is invariant - you must have exactly Any.
This is actually better rewrite as follows:
trait Elem[X, Y] {
def ::[Z, X](other : Elem[Z, X]) : Elem[Z, Y] = other match {
case Chain(head, tail) => Chain(head, tail :: this)
case simple => Chain(simple, this)
}
}
final class Chain[A, B, C](val head : Elem[A, B], val tail : Elem[B, C]) extends Elem[A, C]
object Chain {
def unapply[A,B,C](src : Chain[A,B,C]) : Option[(Elem[A,B], Elem[B,C])] =
Some( (src.head, src.tail) )
def apply[A,B,C](head : Elem[A,B], tail : Elem[B,C]) : Chain[A,B,C] =
new Chain(head, tail)
}
After this error message becoming much more informative and it is obviously how to repair this.
However I don't know why that works for case classes. Sorry.
Working example is:
trait Elem[+X, +Y] {
def ::[Z, XX >: X](other : Elem[Z, XX]) : Elem[Z, Y] = other match {
case Chain(head, tail) => Chain(head, tail :: this)
case simple => Chain(simple, this)
}
}
final class Chain[A, B, C](val head : Elem[A, B], val tail : Elem[B, C]) extends Elem[A, C]
object Chain {
def unapply[A,B,C](src : Chain[A,B,C]) : Option[(Elem[A,B], Elem[B,C])] =
Some( (src.head, src.tail) )
def apply[A,B,C](head : Elem[A,B], tail : Elem[B,C]) : Chain[A,B,C] =
new Chain(head, tail)
}
EDITED:
Eventually I found that:
case class A[T](a: T)
List(A(1), A("a")).collect { case A(x) => A(x) }
// res0: List[A[_ >: String with Int]] = List(A(1), A(a))
class B[T](val b: T)
object B {
def unapply[T](b: B[T]): Option[T] = Option(b.b)
}
List(new B(1), new B("b")).collect { case B(x) => new B(x) }
// res1: List[B[Any]] = List(B#1ee4afee, B#22eaba0c)
Obvious that it is compiler feature. So I think no way there to reproduce the full case class behavior.
I know a lot of questions exist about type erasure and pattern matching on generic types, but I could not understand what should I do in my case from answers to those, and I could not explain it better in title.
Following code pieces are simplified to present my case.
So I have a trait
trait Feature[T] {
value T
def sub(other: Feature[T]): Double
}
// implicits for int,float,double etc to Feature with sub mapped to - function
...
Then I have a class
class Data(val features: IndexedSeq[Feature[_]]) {
def sub(other: Data): IndexedSeq[Double] = {
features.zip(other.features).map {
case(e1: Feature[t], e2: Feature[y]) => e1 sub e2.asInstanceOf[Feature[t]]
}
}
}
And I have a test case like this
case class TestFeature(val value: String) extends Feature[String] {
def sub(other: Feature[String]): Double = value.length - other.length
}
val testData1 = new Data(IndexedSeq(8, 8.3f, 8.232d, TestFeature("abcd"))
val testData2 = new Data(IndexedSeq(10, 10.1f, 10.123d, TestFeature("efg"))
testData1.sub(testData2).zipWithIndex.foreach {
case (res, 0) => res should be (8 - 10)
case (res, 1) => res should be (8.3f - 10.1f)
case (res, 2) => res should be (8.232d - 10.123d)
case (res, 3) => res should be (1)
}
This somehow works. If I try sub operation with instances of Data that have different types in same index of features, I get a ClassCastException. This actually satisfies my requirements, but if possible I would like to use Option instead of throwing an exception. How can I make following code work?
class Data(val features: IndexedSeq[Feature[_]]) {
def sub(other: Data): IndexedSeq[Double] = {
features.zip(other.features).map {
// of course this does not work, just to give idea
case(e1: Feature[t], e2: Feature[y]) if t == y => e1 sub e2.asInstanceOf[Feature[t]]
}
}
}
Also I am really inexperienced in Scala, so I would like to get feedback on this type of structure. Are there another ways to do this and which way would make most sense?
Generics don't exist at runtime, and an IndexedSeq[Feature[_]] has forgotten what the type parameter is even at compile time (#Jatin's answer won't allow you to construct a Data with a list of mixed types of Feature[_]). The easiest answer might be just to catch the exception (using catching and opt from scala.util.control.Exception). But, to answer the question as written:
You could check the classes at runtime:
case (e1: Feature[t], e2: Feature[y]) if e1.value.getClass ==
e2.value.getClass => ...
Or include the type information in the Feature:
trait Feature[T] {
val value: T
val valueType: ClassTag[T] // write classOf[T] in subclasses
def maybeSub(other: Feature[_]) = other.value match {
case valueType(v) => Some(actual subtraction)
case _ => None
}
}
The more complex "proper" solution is probably to use Shapeless HList to preserve the type information in your lists:
// note the type includes the type of all the elements
val l1: Feature[Int] :: Feature[String] :: HNil = f1 :: f2 :: HNil
val l2 = ...
// a 2-argument function that's defined for particular types
// this can be applied to `Feature[T], Feature[T]` for any `T`
object subtract extends Poly2 {
implicit def caseFeatureT[T] =
at[Feature[T], Feature[T]]{_ sub _}
}
// apply our function to the given HLists, getting a HList
// you would probably inline this
// could follow up with .toList[Double]
// since the resulting HList is going to be only Doubles
def subAll[L1 <: HList, L2 <: HList](l1: L1, l2: L2)(
implicit zw: ZipWith[L1, L2, subtract.type]) =
l1.zipWith(l2)(subtract)
That way subAll can only be called for l1 and l2 all of whose elements match, and this is enforced at compile time. (If you really want to do Options you can have two ats in the subtract, one for same-typed Feature[T]s and one for different-typed Feature[_]s, but ruling it out entirely seems like a better solution)
You could do something like this:
class Data[T: TypeTag](val features: IndexedSeq[Feature[T]]) {
val t = implicitly[TypeTag[T]]
def sub[E: TypeTag](other: Data[E]): IndexedSeq[Double] = {
val e = implicitly[TypeTag[E]]
features.zip(other.features).flatMap{
case(e1, e2: Feature[y]) if e.tpe == t.tpe => Some(e1 sub e2.asInstanceOf[Feature[T]])
case _ => None
}
}
}
And then:
case class IntFeature(val value: Int) extends Feature[Int] {
def sub(other: Feature[Int]): Double = value - other.value
}
val testData3 = new Data(IndexedSeq(TestFeature("abcd")))
val testData4 = new Data(IndexedSeq(IntFeature(1)))
println(testData3.sub(testData4).zipWithIndex)
gives Vector()
I have the following class hierarchy:
class A
class B extends A
class C extends A
then, there is another class which takes instances of these classes and there is a method, in which two cases of pattern-matching are possible like this:
class D (one: A, two: A) {
def work {
(one, two) match {
case (o, t): (B, B) => ... blablabla
case (o, t): (B, C) => ... blablabla
case _ =>
}
}
}
However, when it should resolve the matching in favor of the second case (B, C), it tries resolving it as (B, B) and comes up with the class cast exception that C cannot be cast to B. Why? What to do? How can I come around this?
Your syntax isn't quite right (doesn't compile).
This works though:
object Matcher extends App {
class A
class B extends A
class C extends A
class D(one: A, two: A) {
def work {
(one, two) match {
case (o: B, t: B) => println("B")
case (o: B, t: C) => println("C")
case _ =>
}
}
}
val d1 = new D(new B, new B)
val d2 = new D(new B, new C)
d1.work
//B
d2.work
//C
}
The problem, as always, is erased types. (B,C) is syntactic sugar for Tuple2[B,C], which is erased to Tuple2 at runtime. The case statement verifies that (B,C) matches Tuple2, but then fails to cast it.
In your case, the easiest solution would be to match against 'one' and 'two' individually, rather than wrapping them in a tuple:
one match {
case o : B => two match {
case p : C => ...
case p : B => ...
}
...
}
It's not so pretty, but it won't suffer from the same problems.
Edit: Actually, I'd go with Brian Smith's solution - matching inside the tuple rather than outside. It avoids the problem in a similar way, but looks nicer.
I made this code work.
Firstly I added a case to your class definition.
case class A
case class B extends A
case class C extends A
Secondly I changed the work.
class D(one: A, two: A) {
def work {
(one, two) match {
case (o: B, t: B) => println("BB")
case (o: B, t: C) => println("BC")
case (o: C, t: C) => println("CC")
case _ => println("AA")
}
}
}
Now what I got:
new D(B(),B()).work => BB
new D(B(),C()).work => BC
new D(C(),C()).work => CC
new D(A(),B()).work => AA
The case adds an apply and an unapply method.