Stackless Scala With Free Monads, complete example - scala

The following code is adapted from a paper (R. O. Bjarnason, Stackless Scala With Free Monads).
The title of the paper points to the purpose of the proposed data structures in general - that is to afford recursive processing in constant stack space, and to let the user express recursion in a clear way.
Specificly, my goal is to have a monadic structure that affords structural rewriting of an immutable Tree of Pairs (binary tree) or of Lists (n-ary-tree) based on simple pattern matching in constant stack space when ascending.
sealed trait Free[S[+_], +A]{
private case class FlatMap[S[+_], A, +B](
a: Free[S, A],
f: A => Free[S, B]
) extends Free[S, B]
def map[B](f: A => B): Free[S, B] = this.flatMap((a:A) => Done[S, B](f(a)))
def flatMap[B](f: A => Free[S, B]): Free[S, B] = this match {
case FlatMap(a, g) => FlatMap(a, (x: Any) => g(x).flatMap(f))
case x => FlatMap(x, f)
}
#tailrec
final def resume(implicit S: Functor[S]): Either[S[Free[S, A]], A] = {
this match {
case Done(a) => Right(a)
case More(k) => Left(k)
case FlatMap(a, f) => a match {
case Done(a) => f(a).resume
case More(k) => Left(S.map(k)((x)=>x.flatMap(f)))
case FlatMap(b, g) => b.flatMap((x: Any) => g(x).flatMap(f)).resume
}
}
}
}
case class Done[S[+_], +A](a: A) extends Free[S, A]
case class More[S[+_], +A](k: S[Free[S, A]]) extends Free[S,A]
trait Functor[F[+_]] {
def map[A, B](m: F[A])(f: A => B): F[B]
}
type RoseTree[+A] = Free[List, A]
implicit object listFunctor extends Functor[List] {
def map[A, B](a: List[A])(f: A => B) = a.map(f)
}
var tree : Free[List, Int]= More(List(More(List(More(List(Done(1), Done(2))), More(List(Done(3), Done(4))))), More(List(More(List(Done(5), Done(6))), More(List(Done(7), Done(8)))))))
How is the rewriting achieved using Free?
Where is a hook for the pattern matcher? - The pattern matcher has to be exposed to each entire subtree when ascending!
Can this be done within a for block?
[The question was edited.]

Update: the answer below addresses an earlier version of the question, but is mostly still relevant.
First of all, your code isn't going to work as it is. You can either make everything invariant, or go with the variance annotations in the original paper. For the sake of simplicity I'll take the invariant route (see here for a complete example), but I've also just confirmed that the version in the paper will work on 2.10.2.
To answer your first question first: your binary tree type is isomorphic to BinTree[Int]. Before we can show this, though, we need a functor for our pair type:
implicit object pairFunctor extends Functor[Pair] {
def map[A, B](a: Pair[A])(f: A => B) = (f(a._1), f(a._2))
}
Now we can use resume, which we'll need for the conversion from BinTree back to T:
def from(tree: T): BinTree[Int] = tree match {
case L(i) => Done(i)
case F((l, r)) => More[Pair, Int]((from(l), from(r)))
}
def to(tree: BinTree[Int]): T = tree.resume match {
case Left((l, r)) => F((to(l), to(r)))
case Right(i) => L(i)
}
Now we can define your example tree:
var z = 0
def f(i: Int): T = if (i > 0) F((f(i - 1), f(i - 1))) else { z = z + 1; L(z) }
val tree = f(3)
Let's demonstrate our isomorphism and the monad for BinTree by replacing every leaf value with the tree containing its predecessor and successor:
val newTree = to(
from(tree).flatMap(i => More[Pair, Int]((Done(i - 1), Done(i + 1))))
)
After some reformatting, the result will look like this:
F((
F((
F((
F((L(0), L(2))),
F((L(1), L(3)))
)),
F((
F((L(2), L(4))),
F((L(3), L(5)))
)),
...
And so on, as expected.
For your second question: if you want to do the same kind of thing for a rose tree, you'd just replace the pair with a list (or a stream). You'll need to provide a functor instance for lists, as we did above for pairs, and then you've got a tree with Done(x) representing leaves and More(xs) for branches.
Your type needs map for the for-comprehension syntax to work. Fortunately you can write map in terms of flatMap and Done—just add the following to the definition of Free:
def map[B](f: A => B): Free[S, B] = this.flatMap(f andThen Done.apply)
Now the following is exactly the same as the newTree above:
val newTree = to(
for {
i <- from(tree)
m <- More[Pair, Int]((Done(i - 1), Done(i + 1)))
} yield m
)
The same thing will work with the Free[List, _] rose tree.

Related

Recursive higher order function type in Scala 3

I want to define a type for a function that does something and then returns another function of the same type [can be itself]. The obvious idea didn't work ("Illegal cyclic type reference" error):
type Behavior[S] = S => Behavior[S]
Is there something obvious that I am missing here? Also I do not understand how to express an idea of "function returning itself".
Short answer
case class Behavior[S](step: S => Behavior[S])
Long answer (short version)
Terminal F-Coalgebras are pretty cool.
Long answer
Warning: lots of barbed wire & co-bananas, or something...
Ok, so, suppose that you have the concept of a functor F that captures what it means that your behavior "does something". In most libraries is something like this:
trait Functor[F[_]]:
def map[A, B](fa: F[A])(f: A => B): F[B]
An F-coalgebra A is essentially just a function from A to F[A]:
trait FCoalg[F[_]: Functor, A]:
def apply(a: A): F[A]
Now, a terminal F-coalgebra T is an F-coalgebra which additionally has a property that from every other F-coalgebra A there is a mediating morphism A => T (such that everything commutes, blah blah):
trait TerminalFCoalg[F[_]: Functor, T] extends FCoalg[F, T]:
def mediate[A](coalg: FCoalg[F, A]): A => T
Can we implement it for arbitrary F? It turns out we can:
case class TerminalFCoalgCarrier[F[_]: Functor](
step: () => F[TerminalFCoalgCarrier[F]]
)
given tfcImpl[F[_]: Functor]: TerminalFCoalg[F, TerminalFCoalgCarrier[F]] with
def apply(a: TerminalFCoalgCarrier[F]): F[TerminalFCoalgCarrier[F]] = a.step()
def mediate[A](coalg: FCoalg[F, A]): A => TerminalFCoalgCarrier[F] = a =>
TerminalFCoalgCarrier(() => summon[Functor[F]].map(coalg(a))(mediate(coalg)))
For the sake of a concrete example, let's see what that contraption does for the simplest imaginable functor Option:
given Functor[Option] with
def map[A, B](fa: Option[A])(f: A => B): Option[B] = fa.map(f)
type ConaturalNumber = TerminalFCoalgCarrier[Option]
It turns out that the terminal F-coalgebra for Option are the so-called conatural numbers. These are basically the natural numbers, plus countable infinity. These things are nicely suitable for representing lengths of potentially infinite "clicking" processes.
Let's try it on a finite behavior:
enum WelshCounting:
case Eeny
case Meeny
case Miny
case Moe
object WelshCountingOptionCoalg extends FCoalg[Option, WelshCounting]:
def apply(w: WelshCounting): Option[WelshCounting] =
import WelshCounting._
w match
case Eeny => None
case Meeny => Some(Eeny)
case Miny => Some(Meeny)
case Moe => Some(Miny)
val welshMediatingMorphism =
summon[TerminalFCoalg[Option, TerminalFCoalgCarrier[Option]]]
.mediate(WelshCountingOptionCoalg)
Now, the above machinery automatically gives us a universal way to translate those counting words into conatural numbers. Let's add a helper method for describing conatural numbers (approximately):
def describe(c: ConaturalNumber): String =
var counter = 0
var curr = c
while true do
curr.step() match
case None => return s"${counter}"
case Some(next) =>
if counter > 42 then
return "probably infinite"
else {
counter += 1
curr = next
}
throw new Error("We have counted to infinity, yay! :D")
What does it show for the Welsh counting words?
#main def demo(): Unit =
for w <- WelshCounting.values do
val conat = welshMediatingMorphism(w)
println(s"${w} -> ${describe(conat)}")
// Eeny -> 0
// Meeny -> 1
// Miny -> 2
// Moe -> 3
Ok, that's neat. Let's try an infinitely clicking process with just one state that is successor of itself:
object LoopForever extends FCoalg[Option, Unit]:
def apply(u: Unit) = Some(())
val loopForeverMediatingMorphism =
summon[TerminalFCoalg[Option, TerminalFCoalgCarrier[Option]]]
.mediate(LoopForever)
How would it now describe the single state ()?
println(s"${()} -> ${describe(loopForeverMediatingMorphism(()))}")
// () -> probably infinite
Seems to work.
Full code:
trait Functor[F[_]]:
def map[A, B](fa: F[A])(f: A => B): F[B]
trait FCoalg[F[_]: Functor, A]:
def apply(a: A): F[A]
trait TerminalFCoalg[F[_]: Functor, T] extends FCoalg[F, T]:
def mediate[A](coalg: FCoalg[F, A]): A => T
case class TerminalFCoalgCarrier[F[_]: Functor](
step: () => F[TerminalFCoalgCarrier[F]]
)
given tfcImpl[F[_]: Functor]: TerminalFCoalg[F, TerminalFCoalgCarrier[F]] with
def apply(a: TerminalFCoalgCarrier[F]): F[TerminalFCoalgCarrier[F]] = a.step()
def mediate[A](coalg: FCoalg[F, A]): A => TerminalFCoalgCarrier[F] = a =>
TerminalFCoalgCarrier(() => summon[Functor[F]].map(coalg(a))(mediate(coalg)))
given Functor[Option] with
def map[A, B](fa: Option[A])(f: A => B): Option[B] = fa.map(f)
type ConaturalNumber = TerminalFCoalgCarrier[Option]
def describe(c: ConaturalNumber): String =
var counter = 0
var curr = c
while true do
curr.step() match
case None => return s"${counter}"
case Some(next) =>
if counter > 42 then
return "probably infinite"
else {
counter += 1
curr = next
}
throw new Error("We cannot count to infinity :(")
enum WelshCounting:
case Eeny
case Meeny
case Miny
case Moe
object WelshCountingOptionCoalg extends FCoalg[Option, WelshCounting]:
def apply(w: WelshCounting): Option[WelshCounting] =
import WelshCounting._
w match
case Eeny => None
case Meeny => Some(Eeny)
case Miny => Some(Meeny)
case Moe => Some(Miny)
val welshMediatingMorphism =
summon[TerminalFCoalg[Option, TerminalFCoalgCarrier[Option]]]
.mediate(WelshCountingOptionCoalg)
object LoopForever extends FCoalg[Option, Unit]:
def apply(u: Unit) = Some(())
val loopForeverMediatingMorphism =
summon[TerminalFCoalg[Option, TerminalFCoalgCarrier[Option]]]
.mediate(LoopForever)
#main def demo(): Unit =
for w <- WelshCounting.values do
val conat = welshMediatingMorphism(w)
println(s"${w} -> ${describe(conat)}")
println(s"${()} -> ${describe(loopForeverMediatingMorphism(()))}")

Trying to understand scanLeft on trees in Scala

I'm in a Coursera's course and I'm trying to understand the logic of the scanLeft on trees.
we have the following code:
Here we have as an input a tree (without intermediate values, only with values in Leafs) and returns a tree with intermediate values (with values in nodes)
def upsweep[A](t: Tree[A], f: (A,A) => A): TreeRes[A] = t match {
case Leaf(v) => LeafRes(v)
case Node(l, r) => {
val (tL, tR) = parallel(upsweep(l, f), upsweep(r, f))
NodeRes(tL, f(tL.res, tR.res), tR)
}
}
And the following code that given a tree with intermediate values (with values in nodes) returns a tree without intermediate values (a0 is the reduce of all elements left of the tree t).
def downsweep[A](t: TreeRes[A], a0: A, f : (A,A) => A): Tree[A] = t match {
case LeafRes(a) => Leaf(f(a0, a))
case NodeRes(l, _, r) => {
val (tL, tR) = parallel(downsweep[A](l, a0, f),
downsweep[A](r, f(a0, l.res), f))
Node(tL, tR) }
}
And finally the scanLeft code:
def scanLeft[A](t: Tree[A], a0: A, f: (A,A) => A): Tree[A] = {
val tRes = upsweep(t, f)
val scan1 = downsweep(tRes, a0, f)
prepend(a0, scan1)
}
And my question is, why is necesary to use the upsweep method before downsweep?
With upsweep we generate the intermediate values and later with downsweep we "remove" (we dont need to use) them.
Thanks in advance.
Actually look more closely at this part
case NodeRes(l, _, r) => {
val (tL, tR) = parallel(downsweep[A](l, a0, f),
downsweep[A](r, f(a0, l.res), f))
what is l.res? why it is so necessary to have it?(it is created at upsweep) I recommend you to draw on a piece of paper step by step what exactly is being done by this algorithm with easy function like (_ + _). Also it Is very good technique if you do not understand smth to do it, just go easy step by step and resolve it by yourself.

Tail recursive fold on a binary tree in Scala

I am trying to find a tail recursive fold function for a binary tree. Given the following definitions:
// From the book "Functional Programming in Scala", page 45
sealed trait Tree[+A]
case class Leaf[A](value: A) extends Tree[A]
case class Branch[A](left: Tree[A], right: Tree[A]) extends Tree[A]
Implementing a non tail recursive function is quite straightforward:
def fold[A, B](t: Tree[A])(map: A => B)(red: (B, B) => B): B =
t match {
case Leaf(v) => map(v)
case Branch(l, r) =>
red(fold(l)(map)(red), fold(r)(map)(red))
}
But now I am struggling to find a tail recursive fold function so that the annotation #annotation.tailrec can be used.
During my research I have found several examples where tail recursive functions on a tree can e.g. compute the sum of all leafs using an own stack which is then basically a List[Tree[Int]]. But as far as I understand in this case it only works for the additions because it is not important whether you first evaluate the left or the right hand side of the operator. But for a generalised fold it is quite relevant. To show my intension here are some example trees:
val leafs = Branch(Leaf(1), Leaf(2))
val left = Branch(Branch(Leaf(1), Leaf(2)), Leaf(3))
val right = Branch(Leaf(1), Branch(Leaf(2), Leaf(3)))
val bal = Branch(Branch(Leaf(1), Leaf(2)), Branch(Leaf(3), Leaf(4)))
val cmb = Branch(right, Branch(bal, Branch(leafs, left)))
val trees = List(leafs, left, right, bal, cmb)
Based on those trees I want to create a deep copy with the given fold method like:
val oldNewPairs =
trees.map(t => (t, fold(t)(Leaf(_): Tree[Int])(Branch(_, _))))
And then proof that the condition of equality holds for all created copies:
val conditionHolds = oldNewPairs.forall(p => {
if (p._1 == p._2) true
else {
println(s"Original:\n${p._1}\nNew:\n${p._2}")
false
}
})
println("Condition holds: " + conditionHolds)
Could someone give me some pointers, please?
You can find the code used in this question at ScalaFiddle: https://scalafiddle.io/sf/eSKJyp2/15
You could reach a tail recursive solution if you stop using the function call stack and start using a stack managed by your code and an accumulator:
def fold[A, B](t: Tree[A])(map: A => B)(red: (B, B) => B): B = {
case object BranchStub extends Tree[Nothing]
#tailrec
def foldImp(toVisit: List[Tree[A]], acc: Vector[B]): Vector[B] =
if(toVisit.isEmpty) acc
else {
toVisit.head match {
case Leaf(v) =>
val leafRes = map(v)
foldImp(
toVisit.tail,
acc :+ leafRes
)
case Branch(l, r) =>
foldImp(l :: r :: BranchStub :: toVisit.tail, acc)
case BranchStub =>
foldImp(toVisit.tail, acc.dropRight(2) ++ Vector(acc.takeRight(2).reduce(red)))
}
}
foldImp(t::Nil, Vector.empty).head
}
The idea is to accumulate values from left to right, keep track of the parenthood relation by the introduction of a stub node and reduce the result using your red function using the last two elements of the accumulator whenever a stub node is found in the exploration.
This solution could be optimized but it is already a tail recursive function implementation.
EDIT:
It can be slightly simplified by changing the accumulator data structure to a list seen as a stack:
def fold[A, B](t: Tree[A])(map: A => B)(red: (B, B) => B): B = {
case object BranchStub extends Tree[Nothing]
#tailrec
def foldImp(toVisit: List[Tree[A]], acc: List[B]): List[B] =
if(toVisit.isEmpty) acc
else {
toVisit.head match {
case Leaf(v) =>
foldImp(
toVisit.tail,
map(v)::acc
)
case Branch(l, r) =>
foldImp(r :: l :: BranchStub :: toVisit.tail, acc)
case BranchStub =>
foldImp(toVisit.tail, acc.take(2).reduce(red) :: acc.drop(2))
}
}
foldImp(t::Nil, Nil).head
}

Erroneous Scala code using polymorphic ADT type checks

I'm doing some exercises to better understand IO monads (following Functional Programming in Scala), and managed to write some erroneous code that somehow passed the compilation and caused me some headache.
In the below example, I am writing a stack-safe interpreter of an IO monad. The code is in the pattern matching on a polymorphic Algebraic Data Type (FlatMap[A, B]). The error in the code is k1 andThen k2; the two functions can't compose because k1 is returning a different type (IO[B]) than what k2 expects (B). The code still type-checks somehow, and it is obviously a typechecking error because, in the runtime, there is a ClassCastException at an automatic unboxing (just as if I was using an unsafe cast in Java). There are also no compiler warnings issued.
The code (also found on gist):
object IOMonadExercise extends App {
sealed trait IO[A]
case class Return[A](value: A) extends IO[A]
case class Suspend[A](f: () => A) extends IO[A]
case class FlatMap[A, B](io: IO[A], cont: A => IO[B]) extends IO[B]
object IO {
def apply[A](a: => A): IO[A] = Suspend(() => a)
}
object Interpreter {
def run[A](io: IO[A]): A = {
io match {
case Return(a) => a
case Suspend(f) => f()
case FlatMap(Return(a), cont) => run(cont(a))
case FlatMap(Suspend(f), cont) => run(cont(f()))
// this case compiles for whatever reason but shouldn't type check (k1 returns IO[B] and k2 expects just B)
// accordingly, there is a ClassCastException in the runtime
case FlatMap(FlatMap(io1, k1), k2) => run(FlatMap(io1, k1 andThen k2))
// this case is the one that actually works
// case FlatMap(FlatMap(io1, k1), k2) => run(flatten(io1, k1, k2))
}
}
def flatten[A, B, C](io: IO[A], k1: A => IO[B], k2: B => IO[C]): FlatMap[A, C] = {
FlatMap(io, a => FlatMap(k1(a), k2))
}
}
def sum(i: Int): IO[Int] = {
Stream.range(0, i).foldLeft(IO(0))((io, i) => FlatMap(io, (s: Int) => IO(s + i)))
}
val n = 100000
val sumNIO: IO[Int] = sum(n)
val sumN: Int = Interpreter.run(sumNIO)
println(s"sum of 1..$n by IO loop : $sumN")
println(s"sum of 1..$n by math expr: ${n * (n - 1) / 2}")
assert(sumN == n * (n - 1) / 2)
}
What is going on? Is this a compiler bug? Or is this a known limitation of the type inference? Or is there an explanation for this?
I've tested on both Scala 2.11.8 and on 2.12.0, and the behavior seems to be the same: the code compiles without warnings.
I think this is a case of the SI-5195 bug. If you construct the nested FlatMap manually, you cannot write that andThen, because all the types are known and k1 and k2 are obviously not composable.
But in that pattern matching types of io1, k1 and k2 are not known in advance, they have to be inferred and as we see they are inferred wrong. [...]
EDIT
Here is another try to explain how it type-checks: if you start inferring types for k1 and k2 yourself, you will come up with
k1: X => IO[Y] and k2: Y => IO[A] for some X and Y
plus from k1 andThen k2 you need IO[Y] <: Y
So does there exist any type Y which satisfies these restrictions? Yes, it's Any.
But when you apply it, IO[Y] becomes Suspend[Int] and Y is just Int for which subtype relation doesn't hold.

How to reproduce case class behaviour with apply/unapply methods?

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.