typeclass - Problems generalizing Scala type classes -
while working on scala project used type class pattern, ran appears serious problem in how language implements pattern: since scala type-class implementations must managed programmer , not language, variable belonging type-class can never become annotated parent type unless type-class implementation taken it.
to illustrate point, i've coded quick example program. imagine trying write program handle different kinds of employees company , print reports on progress. solve type-class pattern in scala, might try this:
abstract class employee class packer(boxespacked: int, cratespacked: int) extends employee class shipper(trucksshipped: int) extends employee
a class hierarchy modelling different kinds of employees, simple enough. implement reportmaker type-class.
trait reportmaker[t] { def printreport(t: t): unit } implicit object packerreportmaker extends reportmaker[packer] { def printreport(p: packer) { println(p.boxespacked + p.cratespacked) } } implicit object shipperreportmaker extends reportmaker[shipper] { def printreport(s: shipper) { println(s.trucksshipped) } }
that's , good, , can write kind of roster class might this:
class roster { private var employees: list[employee] = list() def reportandadd[t <: employee](e: t)(implicit rm: reportmaker[t]) { rm.printreport(e) employees = employees :+ e } }
so works. now, our type-class, can pass either packer or shipper object reportandadd method, , print report , add employee roster. however, writing method attempt print out report of every employee in roster impossible, without explicitly storing rm object gets passed reportandadd!
two other languages support pattern, haskell , clojure, don't share problem, since deal problem. haskell's stores mapping datatype implementation globally, 'with' variable, , clojure same thing. here's quick example works in clojure.
(defprotocol reporter (report [this] "produce string report of object.")) (defrecord packer [boxes-packed crates-packed] reporter (report [this] (str (+ (:boxes-packed this) (:crates-packed this))))) (defrecord shipper [trucks-shipped] reporter (report [this] (str (:trucks-shipped this)))) (defn report-roster [roster] (dorun (map #(println (report %)) roster))) (def steve (packer. 10 5)) (def billy (shipper. 5)) (def roster [steve billy]) (report-roster roster)
apart rather nasty solution of turning employee list type list[(employee, reportmaker[employee]), scala offer way solve issue? , if not, since scala libraries make extensive use of type-classes, why hasn't been addressed?
the way you'd typically implement algebraic data type in scala case
classes:
sealed trait employee case class packer(boxespacked: int, cratespacked: int) extends employee case class shipper(trucksshipped: int) extends employee
this gives pattern extractors packer
, shipper
constructors, can match on them.
unfortunately, packer
, shipper
distinct (sub)types, part of pattern of encoding algebraic data type in scala disciplined ignoring this. instead, when distinguishing between packer or shipper, use pattern matching in haskell:
implicit object employeereportmaker extends reportmaker[employee] { def printreport(e: employee) = e match { case packer(boxes, crates) => // ... case shipper(trucks) => // ... } }
if have no other types need reportmaker
instance, perhaps type class isn't needed, , can use printreport
function.
Comments
Post a Comment