Reputation: 5844
I borrowed the MyType trick from Landei here. But recently I ran into a problem with the self type. An example shows what I mean:
trait Excitable[SELF] { self: SELF =>
def withMoreAnger: SELF
}
trait Animal[SELF0] { self: SELF0 =>
type SELF = SELF0 // to reveal SELF0 for method spitAt used as a dependent method type
type SpittableAnimal[S] <: Animal[S]
def spitAt[A <: SpittableAnimal[_]](a: A): a.SELF
}
trait ExcitableAnimal[SELF] extends Animal[SELF] with Excitable[SELF] { self: SELF =>
type SpittableAnimal[S] = ExcitableAnimal[S]
def spitAt[A <: SpittableAnimal[_]](a: A): a.SELF = a.withMoreAnger
}
trait Quadruped[SELF] extends ExcitableAnimal[SELF] { self: SELF => }
case class Dog(anger: Int) extends Quadruped[Dog] {
def withMoreAnger: Dog = copy(anger = anger + 1)
}
case class Cat(anger: Int) extends Quadruped[Cat] {
def withMoreAnger: Cat = copy(anger = anger + 1)
}
val dog = Dog(anger = 0)
val cat = Cat(anger = 0)
val angryCat: Cat = dog spitAt cat // fine
val anotherDog = Dog(0)
val animals = Seq(dog, cat)
val angryAnimals: Seq[Quadruped[_]] = for (a <- animals) yield anotherDog spitAt a // fine
val veryAngryAnimals: Seq[Quadruped[_]] = for (a <- angryAnimals) yield anotherDog spitAt a // type mismatch !!!
As far as I can reveal it, the problem seems to be that the underscore in the spitAt
method yields an Any
for a.SELF
in the end. But how can I make this code work?
I also tried this:
def spitAt[A <: SpittableAnimal[A]](a: A): A = a.withMoreAnger
But then inferred type arguments do not conform to method spitAt's type parameter bounds which is clear to me, since the SELF
type argument of the elements in animals
are at least bounded by _ >: Cat with Dog <: Quadruped[_]
which do not conform with a.SELF
, A
in the spitAt
above or even A
in the spitAt
below:
def spitAt[A <: SpittableAnimal[S], S <: A](a: A): A = a.withMoreAnger
So what is the right signature of the spitAt
method to make the for
-loop lines work?
Perhaps variance annotations (+SELF
) of the SELF type parameter may be helpful, but I don´t know how.
Upvotes: 1
Views: 240
Reputation: 5844
Meanwhile I read on and remembered this one: The Typeclass Pattern - An Alternative to Inheritance
And as user1296806 mentioned here, typeclasses are worth a try. So here it is:
trait Excitable[T] { // TYPECLASS
def withMoreAnger(t: T): T
}
trait Animal {
type SpittableAnimal <: Animal
def spitAt[A <: SpittableAnimal: Excitable](a: A): A
}
trait ExcitableAnimal extends Animal {
type SpittableAnimal = ExcitableAnimal
def spitAt[A <: SpittableAnimal: Excitable](a: A): A = implicitly[Excitable[A]] withMoreAnger a
}
object Dog {
implicit object ExcitableDog extends Excitable[Dog] {
def withMoreAnger(dog: Dog): Dog = dog copy (anger = dog.anger + 1)
}
}
case class Dog(anger: Int) extends Quadruped
object Cat {
implicit object ExcitableCat extends Excitable[Cat] {
def withMoreAnger(cat: Cat): Cat = cat copy (anger = cat.anger + 1)
}
}
case class Cat(anger: Int) extends Quadruped
sealed trait Quadruped extends ExcitableAnimal // sealed: to couple pattern match at implicit object ExcitableQuadruped and all subclasses of Quadruped
object Quadruped {
implicit object ExcitableQuadruped extends Excitable[Quadruped] {
def withMoreAnger(quadruped: Quadruped): Quadruped = {
quadruped match {
case dog: Dog => implicitly[Excitable[Dog]].withMoreAnger(dog)
case cat: Cat => implicitly[Excitable[Cat]].withMoreAnger(cat)
}
}
}
}
val dog = Dog(anger = 0)
val cat = Cat(anger = 0)
val angryCat: Cat = dog spitAt cat // fine
val anotherDog = Dog(0)
val animals: Seq[Quadruped] = Seq(dog, cat)
val angryAnimals: Seq[Quadruped] = for (a <- animals) yield anotherDog spitAt a // fine
val podAnimals: Seq[Quadruped] = for (a <- angryAnimals) yield anotherDog spitAt a // fine, still a Seq[Quadruped]
Upvotes: 0
Reputation: 39587
Here's another take. I guess this would be the "spit take."
It just shows variance on the type param, and a revised signature for spitAt.
It also shows an example, in Worm, of that other ageless question, what good is letting you create a concrete class with an abstract type member?
package spit
import language.higherKinds
trait Excitable[+SELF] { self: SELF =>
def withMoreAnger: SELF
}
trait Animal[+SELF] { self: SELF =>
type SpatAt = SELF // to reveal SELF for method spitAt used as a dependent method type
type SpittableAnimal[S] <: Animal[S]
def spitAt[A](a: SpittableAnimal[A]): a.SpatAt
}
trait ExcitableAnimal[+SELF] extends Animal[SELF] with Excitable[SELF] { self: SELF =>
type SpittableAnimal[S] = ExcitableAnimal[S]
def spitAt[A](a: SpittableAnimal[A]): a.SpatAt = a.withMoreAnger
}
trait Quadruped[+SELF] extends ExcitableAnimal[SELF] { self: SELF =>
}
case class Dog(anger: Int) extends Quadruped[Dog] {
def withMoreAnger: Dog = copy(anger = anger + 1)
}
case class Cat(anger: Int) extends Quadruped[Cat] {
def withMoreAnger: Cat = copy(anger = anger + 1)
}
trait Vermiform[SELF] extends Animal[SELF] { self: SELF =>
// worm saliva is actually quite pleasant
def spitAt[A](a: SpittableAnimal[A]): a.SpatAt = a.asInstanceOf[A]
}
case class Worm() extends Vermiform[Worm]
object Test {
def main(args: Array[String]) {
val dog = Dog(anger = 0)
val cat = Cat(anger = 0)
val angryCat: Cat = dog spitAt cat // fine
val anotherDog = Dog(0)
val animals = Seq(dog, cat)
val angryAnimals = for (a <- animals) yield anotherDog spitAt a
val podAnimals = for (a <- angryAnimals) yield anotherDog spitAt a
println(animals)
println(angryAnimals)
println(podAnimals)
val worm = Worm()
//println(worm spitAt dog) // Worms don't spit
}
}
Upvotes: 1
Reputation: 39587
Do you enjoy zee pain? Do you! :)
I can't belief this question enjoys no love.
$ smala spit.Test
List(mild puppy, sweet kitteh)
List(angry puppy, gnarly kitteh)
List(angry hound, gnarly pussy)
Who can resist the gnarly kitteh?
Upvote me or the kitteh become angry! Very angry!
Among the questions about MyType, we find folks saying just use typeclasses. Just do it.
Easy to grant who may spit and be spat upon, and easy to read the code, perhaps.
I meant to get clever, for instance, a really angry cat turns into a GhostCat (anger > 9 lives), but I must go chauffeur at kindergarten...
package spit
import language.{ higherKinds, implicitConversions }
trait Excitable[SELF] { self: SELF =>
def withMoreAnger: SELF
}
trait Animal[SELF] { self: SELF =>
type SpatAt = SELF
type SpittableAnimal[S] <: Animal[S]
}
trait ExcitableAnimal[SELF] extends Animal[SELF] with Excitable[SELF] { self: SELF =>
}
object ExcitableAnimal{
implicit def toSpitter[S](a: ExcitableAnimal[S]) = new Spitter(a)
implicit def toSpittee[S](a: ExcitableAnimal[S]) = new Spittee(a)
}
trait Quadruped[SELF] extends ExcitableAnimal[SELF] { self: SELF =>
}
class Dog(anger: Int) extends Quadruped[Dog] {
def withMoreAnger: Dog = new Dog(anger + 2)
override def toString = s"${if (anger > 0) "angry" else "mild"} ${if (anger > 2) "hound" else "puppy"}"
}
class Cat(anger: Int) extends Quadruped[Cat] {
def withMoreAnger: Cat = new Cat(anger + 1)
override def toString = s"${if (anger > 0) "gnarly" else "sweet"} ${if (anger > 1) "pussy" else "kitteh"}"
}
class Spitter[S](val spitter: Animal[S]) extends AnyVal {
def spitAt[T](spittee: ExcitableAnimal[T]) = spittee.spatUpon
}
class Spittee[S](val spittee: ExcitableAnimal[S]) extends AnyVal {
def spatUpon = spittee.withMoreAnger
}
object Test {
def Dog(anger: Int) = new Dog(anger)
def Cat(anger: Int) = new Cat(anger)
def main(args: Array[String]) {
val dog = Dog(anger = 0)
val cat = Cat(anger = 0)
val angryCat: Cat = dog spitAt cat // fine
val anotherDog = Dog(0)
val animals = Seq(dog, cat)
println(animals)
val angryAnimals = for (a <- animals) yield anotherDog spitAt a
println(angryAnimals)
val poAnimals = for (a <- angryAnimals) yield anotherDog spitAt a
println(poAnimals)
}
}
For reference, another character:
trait Vermiform[SELF] extends Animal[SELF] { self: SELF =>
// worm saliva is actually quite pleasant
def spitAt[A <: SpittableAnimal[A]](a: A): a.SpatAt = a
} // not excitable
case class Worm() extends Vermiform[Worm]
In the car just now, I found myself wondering if worm saliva does in fact have a sedative effect.
Upvotes: 1