Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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 ?

like image 546
Michael Avatar asked Apr 12 '11 13:04

Michael


People also ask

What are type classes used for?

Type classes are a powerful tool used in functional programming to enable ad-hoc polymorphism, more commonly known as overloading.

What are type classes in Scala?

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.

Which of the following we call as type class in Scala?

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.

What do you mean by type class?

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.


4 Answers

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.

like image 132
jsuereth Avatar answered Sep 28 '22 15:09

jsuereth


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!
like image 42
Rex Kerr Avatar answered Sep 28 '22 16:09

Rex Kerr


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!
like image 35
user unknown Avatar answered Sep 28 '22 16:09

user unknown


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).

like image 22
Landei Avatar answered Sep 28 '22 15:09

Landei