开发者

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 Fruits which have always a Writer attached (here Oranges) and you can attach Writers "on the fly" (the last Apple in the List).

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜