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.
Related
This is a sequel to Why can't I run such scala code? (reduceRight List class method)
I am following the accepted answer there to rename my abstract class and add list: List[T] as a parameter of the method foldRight.
abstract class ListT {
def foldRight[U](z : U)(list: List[T], op: (T, U) => U): U = list match {
case Nil => z
case x :: xs => op(x, foldRight(z)(xs, op))
}
}
But I still get the error for the 'def' line
multiple markers at this line, not found: type T
Lets start by scratch, that should help you grasp the concepts.
We will create our own simple functional List.
It will be called MyList, will have an empty list called MyNil and the cons class / operator as :!:.
// Here we create the type MyList.
// The sealed is used to signal that the only valid implementations
// will be part of this file. This is because, a List is an ADT.
// The A (which could be a T or whatever) is just a type parameter
// it means that our list can work with any arbitrary type (like Int, String or My Class)
// and we just give it a name, in order to be able to refer to it in the code.
// Finally, the plus (+) sign, tells the compiler that MyList is covariant in A.
// That means: If A <: B Then MyList[A] <: MyList[B]
// (<: means subtype of)
sealed trait MyList[+A] {
def head: A // Here we say that the head of a List of As is an A.
def tail: MyList[A] // As well, a tail of a List of As is another list of As.
// Here we define the cons operator, to prepend elements to the list.
// You can see that it will just create a new cons class with the new element as the head & this as the tail.
// Now, you may be wondering why we added a new type B and why it must be a super type of A
// You can check out this answer of mine:
// https://stackoverflow.com/questions/54163830/implementing-a-method-inside-a-scala-parameterized-class-with-a-covariant-type/54164135#54164135
final def :!:[B >: A](elem: B): MyList[B] =
new :!:(elem, this)
// Finally, foldRigh!
// You can see that we added a new type parameter B.
// In this case, it does not have any restriction because the way fold works.
final def foldRight[B](z: B)(op: (A, B) => B): B = this match {
case MyNil => z
case h :!: t => op(h, t.foldRight(z)(op))
}
}
object MyList {
// Factory.
def apply[A](elems: A*): MyList[A] =
if (elems.nonEmpty) {
elems.head :!: MyList(elems.tail : _*)
} else {
MyNil
}
}
// Implementations of the MyList trait.
final case class :!:[+A](head: A, tail: MyList[A]) extends MyList[A]
final case object MyNil extends MyList[Nothing] {
override def head = throw new NoSuchElementException("head of empty list")
override def tail = throw new NoSuchElementException("tail of empty list")
}
Now you can:
val l1 = MyList(2, 3, 4) // l1: MyList[Int] = 2 :!: 3 :!: 4 :!: MyNil
val l2 = 1 :!: l1 // // l2: MyList[Int] = 1 :!: 2 :!: 3 :!: 4 :!: MyNil
val sum = l2.foldRight(0)(_ + _) // sum: Int = 10
New to scala. Trying to understand why scala compiler is not happy about the below:
sealed trait LinkedList[A] {
def fold[B](end: B)(func: (A, B) => B): B =
this match {
case End() => end
case Pair(hd, tl) => func(hd, tl.fold(end)(func))
}
def sum: Int =
fold[Int](0){(hd, tl) => hd + tl}
}
final case class Pair[A](head: A, tail: LinkedList[A]) extends LinkedList[A]
final case class End[A]() extends LinkedList[A]
object Foo extends App {
val example = Pair(1, Pair(2, Pair(3, End())))
println(example.sum)
}
Getting this error:
Error:(10, 35) type mismatch;
found : Int
required: String
fold[Int](0){(hd, tl) => hd + tl}
How is String being inferred here?
Please help.
For a general A, usual "addition" is not defined. So instead, it implicitly converts A into String, and uses the + that concatenates Strings. A quick and dirty workaround would be:
def sum(implicit i: A =:= Int): Int = fold[Int](0){(hd, tl) => i(hd) + tl}
This would make sum available only if A is Int. A somewhat more systematic approach would be to use Numeric typeclass, just like the method in the standard library (unfold "use case" and "Full signature").
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()
Suppose I have a list of functions as so:
val funcList = List(func1: A => T, func2: B => T, func2: C => T)
(where func1, et al. are defined elsewhere)
I want to write a method that will take a value and match it to the right function based on exact type (match a: A with func1: A => T) or throw an exception if there is no matching function.
Is there a simple way to do this?
This is similar to what a PartialFunction does, but I am not able to change the list of functions in funcList to PartialFunctions. I am thinking I have to do some kind of implicit conversion of the functions to a special class that knows the types it can handle and is able to pattern match against it (basically promoting those functions to a specialized PartialFunction). However, I can't figure out how to identify the "domain" of each function.
Thank you.
You cannot identify the domain of each function, because they are erased at runtime. Look up erasure if you want more information, but the short of it is that the information you want does not exist.
There are ways around type erasure, and you'll find plenty discussions on Stack Overflow itself. Some of them come down to storing the type information somewhere as a value, so that you can match on that.
Another possible solution is to simply forsake the use of parameterized types (generics in Java parlance) for your own customized types. That is, doing something like:
abstract class F1 extends (A => T)
object F1 {
def apply(f: A => T): F1 = new F1 {
def apply(n: A): T = f(n)
}
}
And so on. Since F1 doesn't have type parameters, you can match on it, and you can create functions of this type easily. Say both A and T are Int, then you could do this, for example:
F1(_ * 2)
The usual answer to work around type erasure is to use the help of manifests. In your case, you can do the following:
abstract class TypedFunc[-A:Manifest,+R:Manifest] extends (A => R) {
val retType: Manifest[_] = manifest[R]
val argType: Manifest[_] = manifest[A]
}
object TypedFunc {
implicit def apply[A:Manifest, R:Manifest]( f: A => R ): TypedFunc[A, R] = {
f match {
case tf: TypedFunc[A, R] => tf
case _ => new TypedFunc[A, R] { final def apply( arg: A ): R = f( arg ) }
}
}
}
def applyFunc[A, R, T >: A : Manifest]( funcs: Traversable[TypedFunc[A,R]] )( arg: T ): R = {
funcs.find{ f => f.argType <:< manifest[T] } match {
case Some( f ) => f( arg.asInstanceOf[A] )
case _ => sys.error("Could not find function with argument matching type " + manifest[T])
}
}
val func1 = { s: String => s.length }
val func2 = { l: Long => l.toInt }
val func3 = { s: Symbol => s.name.length }
val funcList = List(func1: TypedFunc[String,Int], func2: TypedFunc[Long, Int], func3: TypedFunc[Symbol, Int])
Testing in the REPL:
scala> applyFunc( funcList )( 'hello )
res22: Int = 5
scala> applyFunc( funcList )( "azerty" )
res23: Int = 6
scala> applyFunc( funcList )( 123L )
res24: Int = 123
scala> applyFunc( funcList )( 123 )
java.lang.RuntimeException: Could not find function with argument matching type Int
at scala.sys.package$.error(package.scala:27)
at .applyFunc(<console>:27)
at .<init>(<console>:14)
...
I think you're misunderstanding how a List is typed. List takes a single type parameter, which is the type of all the elements of the list. When you write
val funcList = List(func1: A => T, func2: B => T, func2: C => T)
the compiler will infer a type like funcList : List[A with B with C => T].
This means that each function in funcList takes a parameter that is a member of all of A, B, and C.
Apart from this, you can't (directly) match on function types due to type erasure.
What you could instead do is match on a itself, and call the appropriate function for the type:
a match {
case x : A => func1(x)
case x : B => func2(x)
case x : C => func3(x)
case _ => throw new Exception
}
(Of course, A, B, and C must remain distinct after type-erasure.)
If you need it to be dynamic, you're basically using reflection. Unfortunately Scala's reflection facilities are in flux, with version 2.10 released a few weeks ago, so there's less documentation for the current way of doing it; see How do the new Scala TypeTags improve the (deprecated) Manifests?.
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.