Tasks that summarize Scala class classes

While working on a Scala project that used the Type Class template, I came across what seems to be a serious problem in how the language implements the template: since Scala class type implementation should be controlled by the programmer and not the language, any type-type variable , can never be annotated as a parent type, unless its implementation of a type class is taken with it.

To illustrate this point, I encoded a quick sample program. Imagine that you were trying to write a program that could handle various kinds of employees for a company and could print reports on their progress. To solve this problem with a type type template in Scala, you can try something like this:

abstract class Employee class Packer(boxesPacked: Int, cratesPacked: Int) extends Employee class Shipper(trucksShipped: Int) extends Employee 

The class hierarchy, which models different types of employees, is quite simple. Now we are implementing a class like ReportMaker.

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

This is good and good, and now we can write some Roster class that might look like 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 it works. Now, thanks to our class type, we can either pass the wrapper or the sender object to the reportAndAdd method, and it will print the report and add the employee to the list. However, it will be impossible to write a method that tries to print the report of each employee in the list without explicitly storing the rm object, which will be passed to reportAndAdd!

The other two languages ​​that support the template, Haskell and Clojure, do not share this problem, as they deal with this problem. Haskell preserves mapping from a data type to an implementation globally, so it is always "with" a variable, and Clojure basically does the same. Here is a quick example that works great in Clojure.

  (defprotocol Reporter (report [this] "Produce a string report of the 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) 

Besides the rather nasty decision to turn a list of employees into a List [(Employee, ReportMaker [Employee]) type, does Scala offer any way to solve this problem? And if not, since Scala libraries make extensive use of Type-Classes, why wasn't it addressed?

+6
source share
2 answers

The way you usually implement an algebraic data type in Scala will be with case classes:

 sealed trait Employee case class Packer(boxesPacked: Int, cratesPacked: Int) extends Employee case class Shipper(trucksShipped: Int) extends Employee 

This provides template extractors for the Packer and Shipper , so you can map them.

Unfortunately, Packer and Shipper are also different (sub) types, but part of the Scala algebraic data type encoding template should be disciplined to ignore this. Instead, when you distinguish between a packer or a shipper, use pattern matching, as in Haskell:

 implicit object EmployeeReportMaker extends ReportMaker[Employee] { def printReport(e: Employee) = e match { case Packer(boxes, crates) => // ... case Shipper(trucks) => // ... } } 

If you do not have other types for which you need an instance of ReportMaker , then perhaps the type class is not needed, and you can simply use the printReport function.

+5
source

However, it will be impossible to write a method that tries to print the report of each employee in the list without explicitly storing the rm object, which will be passed to reportAndAdd!

Not sure about your specific problem. The following should work (obviously with separate reports concatenated at the output I / O point):

 def printReport(ls: List[Employee]) = { def printReport[T <: Employee](e: T)(implicit rm: ReportMaker[T]) = rm.printReport(e) ls foreach(printReport(_)) } 

However, doing I / O somewhere down the tree-invocation method (or in methods called iteratively) contradicts the “functional philosophy”. It is better to create separate sub-titles as String / List [String] / another exact structure, call all of them to the outermost method and do I / O with one hit. For instance:.

 trait ReportMaker[T] { def generateReport(t: T): String } 

(insert implicit objects similar to Q ...)

 def printReport(ls: List[Employee]) = { def generateReport[T <: Employee](e: T)(implicit rm: ReportMaker[T]): String = rm.generateReport(e) // trivial example with string concatenation - but could do any fancy combine :) someIOManager.print(ls.map(generateReport(_)).mkString("""\n"""))) } 
0
source

Source: https://habr.com/ru/post/951849/


All Articles