Skip to content

Instantly share code, notes, and snippets.

@channingwalton
Created May 8, 2012 20:39
Show Gist options
  • Save channingwalton/2639097 to your computer and use it in GitHub Desktop.
Save channingwalton/2639097 to your computer and use it in GitHub Desktop.
Typeclass in the presence of subtypes
import scala.xml.NodeSeq
object RenderExample {
object Model {
trait Toy
case class Bike extends Toy
case class Train extends Toy
case class Address(number: Int, street: String, postcode: String)
case class Person(name: String, age: Int, address: Address, toy: Toy)
}
object Rendering {
import Model._
trait ExEmEl[A] {
def xml(a: A): NodeSeq
}
object ExEmEl {
implicit object intExEmEl extends ExEmEl[Int] { def xml(i: Int) = <anInt>{ i }</anInt> }
implicit object bikeExEmEl extends ExEmEl[Bike] { def xml(toy: Bike) = <bike/> }
implicit object trainExEmEl extends ExEmEl[Train] { def xml(toy: Train) = <train/> }
implicit def toyExEmEl(implicit b: ExEmEl[Bike], t: ExEmEl[Train]) = new ExEmEl[Toy] {
def xml(toy: Toy) = toy match {
case bike: Bike ⇒ b.xml(bike)
case train: Train ⇒ t.xml(train)
case _ ⇒ NodeSeq.Empty // NO NO NO
}
}
implicit def personToXml(implicit toy: ExEmEl[Toy], add: ExEmEl[Address]) = new ExEmEl[Person] {
def xml(person: Person) =
<person>
<name>{ person.name }</name>
<age>{ person.age }</age>
{ add.xml(person.address) }
{ toy.xml(person.toy) }
</person>
}
implicit def addressToXml: ExEmEl[Address] = new ExEmEl[Address] {
def xml(address: Address) =
<address>
<number>{ address.number }</number>
<street>{ address.street }</street>
<postcode>{ address.postcode }</postcode>
</address>
}
}
object ExEmElRenderer {
def render[A: ExEmEl](value: A) = implicitly[ExEmEl[A]].xml(value)
}
}
}
object Test extends App {
import RenderExample._
import Rendering._
import Model._
val toy: Toy = Bike()
println(ExEmElRenderer.render(Person("Angelica", 4, Address(14, "Orchid Drive", "GU24 9SB"), toy)))
println(ExEmElRenderer.render(3))
// add a new Toy
case class Ball extends Toy
val ball: Toy = Ball()
println(ExEmElRenderer.render(Person("Angelica", 4, Address(14, "Orchid Drive", "GU24 9SB"), ball)))
implicit object ballExEmEl extends ExEmEl[Ball] { def xml(toy: Ball) = <ball/> }
implicit def toyExEmEl(implicit ball: ExEmEl[Ball]) = new ExEmEl[Toy] {
def xml(toy: Toy) = toy match {
case b: Ball ⇒ ball.xml(b)
case other ⇒ ExEmElRenderer.render(other)
}
}
println(ExEmElRenderer.render(Person("Angelica", 4, Address(14, "Orchid Drive", "GU24 9SB"), ball)))
println(ExEmElRenderer.render(Person("Angelica", 4, Address(14, "Orchid Drive", "GU24 9SB"), toy)))
}
@channingwalton
Copy link
Author

Nice to see how terse the Haskell is

@channingwalton
Copy link
Author

The other problem with the final solution above is that the type class instance is fixed at the time the object is constructed which is invariably not where you want the typeclass.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment