0

I use https://pureconfig.github.io/ to load configuration values. For example for each table in a database, I store (db: String, table: String).However, I need to denote specific tables. Therefore, each one has a separate trait. I.e.:

trait Thing
trait ThingWithStuff extends Thing {
    def value:String
}

trait FooThing extends Thing{
    def fooThing: ThingWithStuff
}

trait BarThing extends Thing{
    def barThing: ThingWithStuff
}

They all have a different attribute name with the same type which in return holds i.e. db and table. When processing these with some methods:

def myMethodFoo(thing:FooThing)= println(thing.fooThing)
def myMethodBar(thing:BarThing)= println(thing.barThing)

it leads to code duplication. Trying to fix these using generics I am not able to write a function like:

def myMethod[T<: Thing] = println(thing.thing)

as the attribute name would be different. Is there a smart way around it? Note:

table-first {
db = "a"
table = "b"
}
table-second {
db = "foo"
table = "baz"
}

cannot have the same identifier up front as otherwise it would overwrite each value to hold only the value of the last item for this identifier. Therefore, I resorted to use different attribute names (table-first, table-second or specifically for the example: fooThing, barThing)

How can I fix this issue to prevent code duplication?

Georg Heiler
  • 16,916
  • 36
  • 162
  • 292

1 Answers1

1

Here is a solution using type classes for FooThing and BarThing:

trait Thing

trait ThingWithStuff {
    def value: String
}

trait FooThing extends Thing {
    def fooThing: ThingWithStuff
}

trait BarThing extends Thing {
    def barThing: ThingWithStuff
}

// Define implicits:

trait ThingEx[SomeThing <: Thing] {
  def extract(thing: SomeThing): ThingWithStuff
}

implicit val fooThingEx = new ThingEx[FooThing]{
  def extract(thing: FooThing): ThingWithStuff = thing.fooThing
}

implicit val barThingEx = new ThingEx[BarThing]{
  def extract(thing: BarThing): ThingWithStuff = thing.barThing
}

// Define the method:

def myMethod[SomeThing <: Thing](thing: SomeThing)(implicit thingEx: ThingEx[SomeThing]) =
  println(thingEx.extract(thing).value)

// Try it out:

val foo = new FooThing {
  def fooThing = new ThingWithStuff {
    def value = "I am a FooThing!"
  }
}


val bar = new BarThing {
  def barThing = new ThingWithStuff {
    def value = "I am a BarThing!"
  }
}

myMethod(foo)

myMethod(bar)

Result:

I am a FooThing!
I am a BarThing!

Try it out!

Basically, we "create" polymorphism where there isn't any - the two implicit ThingEx allow you to bind fooThing and barThing together. You only have to define this bind once - and then you can use it everywhere.

If ad-hoc-polymorphism and type classes are new to you, you can start here for example.

I hope this helps!

Markus Appel
  • 3,138
  • 1
  • 17
  • 46
  • Great. Is it possible to generate extractors for a sealed trait automatically? – Georg Heiler Dec 10 '18 at 20:44
  • @GeorgHeiler You could use Shapeless, maybe something like [kitttens](https://github.com/typelevel/kittens)? But I'm not sure. – Markus Appel Dec 10 '18 at 21:24
  • Also, it if I not only want to read (extractors) but also create objects - how would this work? Are there dynamic injectors? – Georg Heiler Dec 10 '18 at 21:43
  • @GeorgHeiler Nothing here is "dynamic". Everything is statically typed and thus typesafe. Take a look at Shapeless' H-Lists, they might be what you're looking for. Otherwise you can always add another function on the `ThingEx` - typeclass, something like `def create(thing: ThingWithStuff): FooThing = new FooThing {... thing ...}`and `def create(thing: ThingWithStuff): BarThing = new BarThingThing {... thing ...}` in `fooThingEx` and `barThingEx`, respectively. – Markus Appel Dec 11 '18 at 08:53