So I was showing a coworker/friend an example of the typeclass pattern in Scala. It looks like this:
case class Song(name: String, artist: String)
case class Address(street: String, number: Int)
trait LabelMaker[T] {
def output(t: T): String
}
object LabelMaker {
implicit object addressLabelMaker extends LabelMaker[Address] {
def output(address: Address) = {
address.number + " " + address.street + " street"
}
}
implicit object songLabelMaker extends LabelMaker[Song] {
def output(song: Song) = {
song.artist + " - " + song.name
}
}
def label[T : LabelMaker](t: T) = implicitly[LabelMaker[T]].output(t)
}
Which can be used like this:
import LabelMaker._
println(label(new Song("Hey Ya", "Outkast"))) // Outkast - Hey Ya
println(label(new Address("Smithsonian", 273))) // 273 Smithsonian street
It's not the best example, and in retrospect I wish I'd come up with a better one. Upon showing him, he responded with a counter example, and asked what benefits the typeclass pattern actually brings to the table:
case class Song(name: String, artist: String)
case class Address(street: String, number: Int)
object LabelMaker {
def label(address: Address) = {
address.number + " " + address.street + " street"
}
def label(song: Song) = {
song.artist + " - " + song.name
}
}
import LabelMaker._
println(label(new Song("Hey Ya", "Outkast"))) // Outkast - Hey Ya
println(label(new Address("Smithsonian", 273))) // 273 Smithsonian street
I struggled to answer that properly, and it made me realise I don't quite understand the gains made 100%. I understand their implementation and very localized benefits when someone else uses them, but to actually succinctly explain them is quite difficult. Can anyone help me? And perhaps extend on my example to really show the benefits.
Typeclasses capture the notion of retroactive extensibility. With static method overloads, you have to define them all at once in one place, but with typeclasses you can define new instances anytime you want for any new types in any modules. For e.g.
object LabelMaker {
// ... your original cases here ...
def label[T : LabelMaker](t: T) = implicitly[LabelMaker[T]].output(t)
}
// somewhere in future
object SomeModule {
import LabelMaker._
case class Car(title: String)
implicit object carLabelMaker extends LabelMaker[Car] {
def output(car: Car) = car.title
}
}
object Main extends App {
import LabelMaker._
import SomeModule._
println(label(Car("Mustang")))
}
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