This is for purely pedagogical purposes only. So, within the contrived data structure and its constraints, I’m interested in knowing how might one improve that reduce function and whether I’ve properly used the Semigroup typeclass correctly here.
object ComplexData { import cats.Semigroup import cats.instances.all._ case class Element(name: String, value: Int, els: List[Element] = Nil) { def reduce[A : Semigroup](z: A)(f: Element => A): A = { Semigroup[A].combine(f(this), els match { case Nil => z case _ => els.map(_.reduce(z)(f)).reduce((a, b) => Semigroup[A].combine(a, b)) }) } } val e1 = Element("Zero", 0, List( Element("One", 1, List(Element("Two", 2, List( Element("Three", 3), Element("Four", 4))))), Element("Five", 5, List( Element("Six", 6), Element("Seven", 7), Element("Eight", 8, List( Element("Nine", 9), Element("Ten", 10))))))) e1.reduce(0)(_.value) //res0: Int = 55 e1.reduce("")(_.name + " ") //res1: String = Zero One Two Three... e1.reduce(0)(_.els.length) //res2: Int = 10 e1.reduce("")(e => e.name + " -> " + e.value + ", ") //res3: String = One -> 1, Two -> 2, Three -> 3, ... }
Specifically:
- While it works, not excited by the use of view bounds given that they are long since deprecated (attempting to use
A <: Semigroup[A]
in the function signature did not compile), do I really need an implicit definition here of the semigroup if I wanted to go this way? - That pattern match seems accidentally complex, even given my constraints there’s probably a more elegant or at least more straightforward way to do that, yes?
- If I used a
Monoid[A]
instead of a semigroup I could get rid of thez
parameter and provide aZero[A]
orEmpty[A]
, I think – is that the preferred way to go?