0

So I have the following scenario. First a class hierarchy A <- B <- C

trait A
trait B extends A
class C extends B

I also have a class that has a generic parameter defined as [T <: A]

class Foo[T <: A]

This class defines two methods that accept objects of type T

def single(t: T): Bar
def multiple(lt: List[T]): Bars

If the objects t and lt are of a type derived from B both, of these methods call another method that takes t and lt (mapping over its elements), does some modification to it (which depends on the type itself) and spits the same object out

def update[P <: B](op: P, item: Item): P = {
  if (item.field1..) op.field1 = item.field1
  if (item.field2..) op.field2 = item.field2
  ...

  op match {
    case c: C => 
      if (item.field123 ...) c.field123 = item.field123
  }

  op
}

I don't like the way this method is right now as it does mutation on the objects. As I am following a functional programming style I have managed to keep the codebase immutable and composable.

I would prefer to push the field assignments to constructors and build up the objects based on their parent class but due to Scala's auxiliary constructor calling scheme I cannot find a way to do this. Another way would have been to have a case class and use the copy constructor but I cannot do that because I need to keep A, B and C extendable.

I hope I managed to describe my scenario well enough. If you need some more clarification - please ask. I would appreciate any help with this one.

zaxme
  • 885
  • 8
  • 26

1 Answers1

0

Typeclasses to the rescue

object Example {

  trait Item

  // generic interface that makes possible for T
  // to be updated by an Item
  // Better to return a new object here instead of mutating an old one
  trait Updater[T] {
    def update(obj: T, item: Item): T
  }

  class B1

  class B2

  implicit val b1Updated: Updater[B1] = new Updater[B1] {
    override def update(obj: B1, item: Item): B1 = {
      println("B1 updater called")
      //your code for b1 here
      ???
    }
  }

  implicit val b2Updated: Updater[B2] = new Updater[B2] {
    override def update(obj: B2, item: Item): B2 = {
      println("B2 updater called")
      //your code for b2 here
      ???
    }
  }

  //whenever you add a new class that has to be updatable you must provide such instance of Updater[MyNewB]
  //But you don't have to declare it right here so it's still modular


  //works for every type T that has implicit instance of type Updater[T]
  def updateOne[T: Updater](obj: T, item: Item): T = {
    implicitly[Updater[T]].update(obj, item)
  }

  def updateList[T: Updater](lst: List[T], item: Item): List[T] = {
    val updater = implicitly[Updater[T]]
    lst.map(e => updater.update(e, item))
  }

  def main(args: Array[String]): Unit = {
    val list: List[B1] = List(new B1, new B1)
    updateList(list, new Item {})
  }
}
simpadjo
  • 3,711
  • 1
  • 13
  • 33
  • this is great but now my tests complain that they "cannot find an implicit value". As I am writing a library I would like not to force the clients to add implicit imports. I read that `implicitly` would resolve the implicits provided they are in scope but so far that doesn't happen. – zaxme Apr 16 '18 at 07:17
  • `implicitly` does resolve implicit values in scope. If not - then values are out of scope. Here https://stackoverflow.com/questions/5598085/where-does-scala-look-for-implicits you can read about where the compiler looks for implicit values. In you case it could be convenient to put implicit values into `object Updater{...}` – simpadjo Apr 16 '18 at 08:18