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 ?
Type classes are a powerful tool used in functional programming to enable ad-hoc polymorphism, more commonly known as overloading.
A type class is an abstract, parameterized type that lets you add new behavior to any closed data type without using sub-typing. If you are coming from Java, you can think of type classes as something like java.
Int, String, and Long are the examples of types we are referring to here. Ad-hoc and Parametric Polymorphism – we covered these in our article about polymorphism in Scala.
In computer science, a type class is a type system construct that supports ad hoc polymorphism. This is achieved by adding constraints to type variables in parametrically polymorphic types.
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
).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With