Created
May 8, 2012 20:39
-
-
Save channingwalton/2639097 to your computer and use it in GitHub Desktop.
Typeclass in the presence of subtypes
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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))) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.