Question about type classes in Scala
Let there are classes Fruit
, Orange
, and Apple
.
abstract class Fruit
class Orange extends Fruit
class Apple extends Fruit
Now I want to add write
functionality to both types Orange
and Apple
. Using the type class pattern I can do the following:
trait Writer[T] {def write(t:T)}
implicit object AppleWriter extends Writer[Apple] {
def write(a:Apple) {println("I am an apple!")}
}
implicit object OrangeWriter extends Writer[Orange] {
def write(o:开发者_如何学运维Orange) {println("I am an orange!")}
}
def write[T](t:T)(implicit w:Writer[T]){w.write(t)}
So for, so good but what if I want to define writeFruits
?
def writeFruits(fruits:List[Fruit]) {for (fruit <- fruits) write(fruit)}
I would like writeFruits
to call either write[Apple]
or write[Orange]
for each fruit
. I see that it does not work (and I know why) but maybe I can implement the writeFruits
anyway.
Can I implement writeFruits
somehow ?
In the instance of covariant/contravariant types, you almost need to define your type class on the "base" type here:
implicit object FruitWriter extends Writer[Fruit] {
def write(a : Fruit) = a match {
case _ : Apple => println("I am an apple!")
case _ : Orange => println("I am an orange")
}
}
You could also work on defining the type class with variance so that Writer[Fruit] could be used when you need a Writer[Apple]. It's unfortunate, but if you want to use OO polymorphism, you have to encode that into the functional aspects.
*strong text*Another option is to use an HList for write-fruits and do all the type-recursion yourself...
Assuming:
trait HList
object HNil extends HList
case class ::[A, Rest <: HList](head : A, tail : Rest)
Then we can do something fun like:
implicit def nilWriter = new Writer[HNil] = { def write(o : HNil) = () }
implicit def hlistWriter[A, Rest](implicit aw : Writer[A], rw : Writer[Rest]) =
new Writer[A :: Rest] {
def write(o : (A :: Rest)) = {
aw.write(o.head)
rw.write(o.tail)
}
}
NOW
write( new Orange :: new Apple :: HNil)
Note: I have not tested this code, but the concept of recursively spanning types is sound. I'm not actually recommending this approach.
You need to pick out only those Fruit
for which a Writer
exists. Unfortunately, once you've cast to Fruit
you've lost the ability to automatically figure out which is which. If you must set up the problem this way--rather than assembling a list of writable fruit or somesuch--then one reasonable option is to split out the types again with a FruitWriter
:
def writeOne[T](t:T)(implicit w:Writer[T]){w.write(t)} // New name to avoid shadowing
implicit object FruitWriter extends Writer[Fruit] {
def write(f: Fruit) { f match {
case o: Orange => writeOne(o)
case a: Apple => writeOne(a)
}}
}
scala> val fruits = List(new Apple, new Orange)
fruits: List[Fruit] = List(Apple@1148ab5c, Orange@39ea2de1)
scala> for (fruit <- fruits) writeOne(fruit)
I am an apple!
I am an orange!
Or maybe case-classes are for you?
abstract class Fruit {}
case object Orange extends Fruit
case object Apple extends Fruit
trait Writer[T] {def write (t:T)}
implicit object FruitWriter extends Writer [Fruit] {
def write (fruit: Fruit) = fruit match {
case Apple => println ("I am an apple!")
case Orange => println ("I am an orange!")
}
}
def writeFruits (fruits: List[Fruit]) {
for (fruit <- fruits) write(fruit)
}
val fl = List (Orange, Apple, Apple, Orange, Apple)
writeFruits (fl)
I am an orange!
I am an apple!
I am an apple!
I am an orange!
I am an apple!
This is not exactly what you want, but gives you a lot of freedom to build your hiearchy:
sealed trait Fruit
case class Orange extends Fruit with OrangeWriter
case class Apple extends Fruit
case class Banana extends Fruit
trait Writer {
def write()
}
trait AppleWriter extends Writer {
self: Apple =>
def write() {println("I am an apple!")}
}
trait OrangeWriter extends Writer {
self: Orange =>
def write() {println("I am an orange!")}
}
def writeFruits(fruits:List[Fruit]) {
fruits.collect{case w:Writer => w}.foreach(_.write())
}
writeFruits(List(Apple(), Orange(),Banana(), new Apple with AppleWriter))
As you can see, you can have Fruit
s which have always a Writer
attached (here Orange
s) and you can attach Writers "on the fly" (the last Apple
in the List
).
精彩评论