开发者

Question about type classes in Scala

开发者 https://www.devze.com 2023-02-25 13:20 出处:网络
Let there are classes Fruit, Orange, and Apple. abstract class Fruit class Orange extends Fruit class Apple extends Fruit

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

精彩评论

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