TL;DR
Given a hierarchy of case classes, a tree of instances can be constructed:
How do I convert that into "something else" via appropriate builders in a type safe (and user friendly) manner (without touching or altering the respective case classes) ?
Update 2021-02-25
I could make it somehow work with
import org.scalajs.dom.console
trait Root
case class Bob(name: String, as: Seq[Root]) extends Root
case class Charles(value: Int, as: Seq[Root]) extends Root
trait Builder[T] {
// can not make T covariant, as this is obj is not in a covariant position
def build(obj: T) : String
}
object Foo {
var r: Map[Root, Builder[Root]] = Map()
def attachBuilder[T <: Root](a: T, builder: Builder[T]) : Unit = {
val e = (a, builder)
r = r + e.asInstanceOf[(Root, Builder[Root])]
}
def b(name : String, as: Root*)(implicit builderB: Builder[Bob]): Bob = {
val b = Bob(name, as)
attachBuilder(b, builderB)
b
}
def c(value: Int, as: Root*)(implicit builderC: Builder[Charles]): Charles = {
val c = Charles(value, as)
attachBuilder(c, builderC)
c
}
def build(obj: Root) : String = {
r(obj).build(obj)
}
}
object UseMe {
implicit val builderB: Builder[Bob] = new Builder[Bob] {
override def build(obj: Bob): String = {
obj.name.toString + obj.as.map(a => Foo.build(a)).mkString(" ")
}
}
implicit val builderC: Builder[Charles] = new Builder[Charles] {
override def build(obj: Charles): String = {
obj.value.toString + obj.as.map(a => Foo.build(a)).mkString(" ")
}
}
def yahoo() : Unit = {
val x = Foo.b("martin", Foo.b("b2"), Foo.c(127))
console.log("This is x: " + Foo.build(x))
}
}
Still, I am very unsatisfied. Idea, I followed: Have a map that catches the respective builder for Bob or Charles. Still,
Builder[T] can not be made covariant. This prevents Builder[Bob] to be a subtype of Builder[Root]
I do not see the proper type signature for Map, so I had to typecast.
Requirements
Hierarchy of Root, Bob, Alice is extensible (so can not be sealed)
No use of static typing (by means of Shapeless HList or similar) as I simply will not have the full types as I am doing computations to assemble the tree.
Questions
What is a better approach?
Original Post
Prelude
Sigh .... mind-bending waste of hours ....... I seriously need your help!
Scenario
Given an ADT
trait A
case class B(name: String, as: Seq[A]) extends A
case class C(value: Int, as: Seq[A]) extends A
that is expected to be extended (not sealed).
Further, assume a
trait Builder[T] {
def build(obj: T) : String
}
Furthermore, with the code below we have "the creator functions" that expect the appropriate builders to in scope.
object Foo {
def b(name : String, as: A*)(implicit builderB: Builder[B]): B = {
???
// How to link ADT instance B with builder for B in a type-safe manner?
// How to trigger builder for `as`: Seq[A] ?
}
def c(value: Int, as: A*)(implicit builderC: Builder[C]): C = {
???
// How to link ADT instance C with builder for C in a type-safe manner?
}
}
With that I want to be able, after defining appropriate builders as implicit vals for B and C to do
object UseMe {
implicit val builderB: Builder[B] = new Builder[B] {
override def build(obj: B): String = {
obj.toString
// and build the as
}
}
implicit val builderC: Builder[C] = new Builder[C] {
override def build(obj: C): String = {
obj.value.toString
// and also build the as
}
}
val x = Foo.b("martin", Foo.b("b2"), Foo.c(127))
// Questions
// How to create a string representation (that is what the builder is doing) for x
// Something like:
// build(x)
}
By intention, I removed all my misleading tries in code, also not to induce any bias.
Tries
A builder impl that uses dynamic type information (via case b: B => ...) is working, but as I expect the ADT to be extended, this is not an option.
All my tries to model by generic types have failed. (Approaches with HList (Shapeless) might be feasible but are not considered, as I think this can be solved in plain Scala)
Questions
How to define methods in Foo?
How to solve builder pattern / creational pattern best for ADTs?
Looking forward to your answers!
Related
I am trying to create a factory pattern, and the return type is a parametrized trait, and the actual return type will be a subtype of that trait, but I do not know the generic type beforehand, so I don't know how to specify the return type. A simplified version is like this:
trait Mapper[T] {
def createMap(a: T, b: T): T
}
class MapperA extends Mapper[String]{
override def createMap(a: String, b: String): String = {
a + b
}
}
class MapperB extends Mapper[Int]{
override def createMap(a: Int, b: Int): Int = {
a + b + b + b
}
}
def factory(typeOfMapper: String): Mapper = {
typeOfMapper match {
case "mapperA" => new MapperA()
case "mapperB" => new MapperB()
}
}
val mapper = factory("mapperA")
This will give me and error of
trait Mapper takes type parameters
but I do not know the generic type beforehand. What should the return type be for the factory method here?
Well, you could return Mapper[_] ... that will compile, but isn't really very useful, as pointed out in the comments: you won't be able to use the returned value in any meaningful way, because actual type of create will be unknown.
If different instances of your mapper will always have different type parameters (at least, within the same scope), then a neat solution would be using implicits:
object Mapper {
implicit def strMapper: Mapper[String] = new MapperA
implicit def intMapper: Mapper[Int] = new MapperB
}
Then everywhere you have these visible, you can just do:
val m1: Mapper[String] = implicitly[Mapper[String]]
val m2: Mapper[Int] = implicitly[Mapper[Int]]
You could also write your factory function (though, I am not sure why you'd want to) like this:
def mapperFactory[T: Mapper] = implicitly[Mapper[T]]
and use it like this:
val m: Mapper[String] = mapperFactory
or like this
def intMapper = mapperFactory[Int]
If you want different mappers for the same type parameter, it's basically the same idea, except it does't look as neat without implicits. The key is different factories for different types:
class Mapper {
def str(`type`: String): Mapper[String] = `type` match {
case "foo" => FooMapper()
case "bar" => BarMapper()
}
def int(`type`: String): Mapper[Int] = `type` match {
case "baz" => BazMapper()
case "bak" => BakMapper()
}
}
val fooMapper = Mapper.str("foo")
val bakMapper = Mapper.int("bak")
I'm using two Scala libraries that both rely on implicit parameters to supply codecs/marshallers for case classes (the libraries in question are msgpack4s and op-rabbit). A simplified example follows:
sealed abstract trait Event
case class SomeEvent(msg: String) extends Event
case class OtherEvent(code: String) extends Event
// Assume library1 needs Show and library2 needs Printer
trait Show[A] { def show(a: A): String }
trait Printer[A] { def printIt(a: A): Unit }
object ShowInstances {
implicit val showSomeEvent = new Show[SomeEvent] {
override def show(a: SomeEvent) =
s"SomeEvent: ${a.msg}"
}
implicit val showOtherEvent = new Show[OtherEvent] {
override def show(a: OtherEvent) =
s"OtherEvent: ${a.code}"
}
}
The Printer for the one library can be generic provided there's an implicit Show for the other library available:
object PrinterInstances {
implicit def somePrinter[A: Show]: Printer[A] = new Printer[A] {
override def printIt(a: A): Unit =
println(implicitly[Show[A]].show(a))
}
}
I want to provide an API that abstracts over the details of the underlying libraries - callers should only need to pass the case class, internally to the API implementation the relevant implicits should be summoned.
object EventHandler {
private def printEvent[A <: Event](a: A)(implicit printer: Printer[A]): Unit = {
print("Handling event: ")
printer.printIt(a)
}
def handle(a: Event): Unit = {
import ShowInstances._
import PrinterInstances._
// I'd like to do this:
//EventHandler.printEvent(a)
// but I have to do this
a match {
case s: SomeEvent => EventHandler.printEvent(s)
case o: OtherEvent => EventHandler.printEvent(o)
}
}
}
The comments in EventHandler.handle() method indicate my issue - is there a way to have the compiler select the right implicits for me?.
I suspect the answer is no because at compile time the compiler doesn't know which subclass of Event handle() will receive, but I wanted to see if there's another way. In my actual code, I control & can change the PrinterInstances code, but I can't change the signature of the printEvent method (that's provided by one of the libraries)
*EDIT: I think this is the same as Provide implicits for all subtypes of sealed type. The answer there is nearly 2 years old, I'm wondering if it's still the best approach?
You have to do the pattern matching somewhere. Do it in the Show instance:
implicit val showEvent = new Show[Event] {
def show(a: Event) = a match {
case SomeEvent(msg) => s"SomeEvent: $msg"
case OtherEvent(code) => s"OtherEvent: $code"
}
}
If you absolutely need individual instances for SomeEvent and OtherEvent, you can provide them in a different object so they can be imported separately.
If Show is defined to be contravariant (i.e. as trait Show[-A] { ... }, with a minus on the generic type) then everything works out of the box and a Show[Event] is usable as a Show[SomeEvent] (and as a Show[OtherEvent] for that matter).
If Show is unfortunately not written to be contravariant, then we might have to do a little bit more juggling on our end than we'd like. One thing we can do is declare all of our SomeEvent values as simply Events, vis a vis val fooEvent: Event = SomeEvent("foo"). Then fooEvent will be showable.
In a more extreme version of the above trick, we can actually hide our inheritance hierarchy:
sealed trait Event {
def fold[X]( withSomeEvent: String => X,
withOtherEvent: String => X ): X
}
object Event {
private case class SomeEvent(msg: String) extends Event {
def fold[X]( withSomeEvent: String => X,
withOtherEvent: String => X ): X = withSomeEvent(msg)
}
private case class OtherEvent(code: String) extends Event {
def fold[X]( withSomeEvent: String => X,
withOtherEvent: String => X ): X = withOtherEvent(code)
}
def someEvent(msg: String): Event = SomeEvent(msg)
def otherEvent(code: String): Event = OtherEvent(code)
}
Event.someEvent and Event.otherEvent allow us to construct values, and fold allows us to pattern match.
I have different sources and corresponding parameters
Source1, Source2, Source3
Parameter1, Parameter2, Parameter3,
Source: is trait (can be changed)
trait Source[T] {
def get(Parameter)(implicit c: Context): MyData[T]
}
Parameter is also a trait
trait Parameter
I have different OutputType class: T1, T2, T3
I need output as: MyData[OutputType]
Fixed API signature (changes to the signature not quite preferable):
val data1: MyData[T1] = MyAPI.get[T1](Parameter1("a", "b")) // this should give MyData from Source1 of type T1
val data2: MyData[T2] = MyAPI.get[T2](Parameter3(123)) // this should give MyData from Source3 of type T2
Some source supports some output types (say T1, T2), but some may not.
What I did:
I tried using scala reflection typeTag to determine the type at runtime, but since return type will be MyData[T], and is in contra-variant position, it wont know the actual return type. (Why does TypeTag not work for return types?)
e.g.
object MyAPI {
get[T: TypeTag](p: Parameter)(implicit c: Context): MyData[T] = {}
}
I also tried using type-class pattern. Scala TypeTag Reflection returning type T
I can work with different OutputType creating implicit val for each, but would only work for single Source1. I can't manage to work for all sources.
I was trying to do:
object MyAPI {
get[T: SomeConverter](p: Parameter)(implicit c: Context): MyData[T] = {
p match {
case Parameter1 => Source1[T].read(p.asInstanceOf(Parameter1)
case Parameter2 => Source2[T].read(p.asInstanceOf(Parameter2)
}
}
}
Disclaimer: I think I figured out what you want. I'm also learning to design type-safe APIs, so here's one.
Provided variant uses implicits. You have to manually establish mapping between parameter types and results they yield, which may or may not include sources. It does not work on runtime, however, so I also removed common trait Parameter. It also does not impose any restrictions on the Sources at all.
It also "looks" the way you wanted it to look, but it's not exactly that.
case class User(id: Int) // Example result type
// Notice I totally removed any and all relation between different parameter types and sources
// We will rebuild those relations later using implicits
object Param1
case class Param2(id: Int)
case class Param3(key: String, filter: Option[String])
// these objects have kinda different APIs. We will unify them.
// I'm not using MyData[T] because it's completely irrelevant. Types here are Int, User and String
object Source1 {
def getInt = 42
}
object Source2 {
def addFoo(id: Int): Int = id + 0xF00
def getUser(id: Int) = User(id)
}
object Source3 {
def getGoodInt = 0xC0FFEE
}
// Finally, our dark implicit magic starts
// This type will provide a way to give requested result for provided parameter
// and sealedness will prevent user from adding more sources - remove if not needed
sealed trait CanGive[Param, Result] {
def apply(p: Param): Result
}
// Scala will look for implicit CanGive-s in companion object
object CanGive {
private def wrap[P, R](fn: P => R): P CanGive R =
new (P CanGive R) {
override def apply(p: P): R = fn(p)
}
// there three show how you can pass your Context here. I'm using DummyImplicits here as placeholders
implicit def param1ToInt(implicit source: DummyImplicit): CanGive[Param1.type, Int] =
wrap((p: Param1.type) => Source1.getInt)
implicit def param2ToInt(implicit source: DummyImplicit): CanGive[Param2, Int] =
wrap((p: Param2) => Source2.addFoo(p.id))
implicit def param2ToUser(implicit source: DummyImplicit): CanGive[Param2, User] =
wrap((p: Param2) => Source2.getUser(p.id))
implicit val param3ToInt: CanGive[Param3, Int] = wrap((p: Param3) => Source3.getGoodInt)
// This one is completely ad-hoc and doesn't even use the Source3, only parameter
implicit val param3ToString: CanGive[Param3, String] = wrap((p: Param3) => p.filter.map(p.key + ":" + _).getOrElse(p.key))
}
object MyApi {
// We need a get method with two generic parameters: Result type and Parameter type
// We can "curry" type parameters using intermediate class and give it syntax of a function
// by implementing apply method
def get[T] = new _GetImpl[T]
class _GetImpl[Result] {
def apply[Param](p: Param)(implicit ev: Param CanGive Result): Result = ev(p)
}
}
MyApi.get[Int](Param1) // 42: Int
MyApi.get[Int](Param2(5)) // 3845: Int
MyApi.get[User](Param2(1)) // User(1): User
MyApi.get[Int](Param3("Foo", None)) // 12648430: Int
MyApi.get[String](Param3("Text", Some(" read it"))) // Text: read it: String
// The following block doesn't compile
//MyApi.get[User](Param1) // Implicit not found
//MyApi.get[String](Param1) // Implicit not found
//MyApi.get[User](Param3("Slevin", None)) // Implicit not found
//MyApi.get[System](Param2(1)) // Same. Unrelated requested types won't work either
object Main extends App {
sealed trait Parameter
case class Parameter1(n: Int) extends Parameter with Source[Int] {
override def get(p: Parameter): MyData[Int] = MyData(n)
}
case class Parameter2(s: String) extends Parameter with Source[String] {
override def get(p: Parameter): MyData[String] = MyData(s)
}
case class MyData[T](t: T)
trait Source[T] {
def get(p: Parameter): MyData[T]
}
object MyAPI {
def get[T](p: Parameter with Source[T]): MyData[T] = p match {
case p1: Parameter1 => p1.get(p)
case p2: Parameter2 => p2.get(p)
}
}
val data1: MyData[Int] = MyAPI.get(Parameter1(15)) // this should give MyData from Source1 of type T1
val data2: MyData[String] = MyAPI.get(Parameter2("Hello World")) // this should give MyData from Source3 of type T2
println(data1)
println(data2)
}
Does this do what you want?
ScalaFiddle: https://scalafiddle.io/sf/FrjJz75/0
I have a library where an abstract class Base[T] is over a type T supplied by the user. There are many specific Base[T] sub-classes, some are over types T and S, like Specific[T, S], but this is irrelevant. The user might specify any T of course while creating and instance, but I want to treat it as T with a trait AdditionalAbilities or in other words I want to 'gift' the user's type with AdditionalAbilities. How can I do that in Scala? I hope the title is correct for this question.
Example (might not be syntactically correct)
class Specific[T **with trait Additional**]() extends Base[T](){
def doSomething() : T = {
val something = new T()
something.ability(2)
println(something.additional)
something
}
}
trait Additional{
var additional : Integer
def ability(i : Integer) : Unit = {
additional = i
}
}
Would work with any T.
When you define a parametric class you can require the parameter type to descend from a certain type:
trait AdditionalAbilities {
def doStuff(): Unit = println("Hey There")
}
object NoAbility extends AdditionalAbilities {
override def doStuff(): Unit = ()
}
abstract class Base[T] { ... }
class Specific[T <: AdditionalAbilities] extends Base[T] {
def f(t: T): Unit = t.doStuff()
}
Then when you try to instantiate a Specific type:
scala> new Specific[Int] {}
<console>:13: error: type arguments [Int] do not conform to class Specific's type parameter bounds [T <: AdditionalAbilities]
scala> val b = new Specific[NoAbility.type] {}
b: Specific[NoAbility.type] = $anon$1#517cd4b
scala> b.f(NoAbility)
//did nothing
Also, if you want to add a behaviour to an existing concrete class, you can do so at the time of instantiation:
trait CoolAbilities { def doStuff(): Unit = println("Hey there") }
class A { }
scala> val a = new A with CoolAbilities
a: A with CoolAbilities = $anon$1#6ad3381f
scala> a.doStuff()
Hey there
Perhaps implicit classes could help? Implicit classes allow you to add functionality to an existing type without needing to modify the existing type, or be the one instantiating it (so that you could mix in a trait).
The following compiles, and prints: 3
class Specific[T] {
implicit class TAdditional(t: T) {
var additional: Integer = 0
def ability(i: Integer) = {
additional = i
}
}
def doSomething(t: T) = {
doSomethingAdditional(t)
}
private def doSomethingAdditional(t: TAdditional) = {
t.ability(3)
println(t.additional)
}
}
val s = new Specific[Int]
s.doSomething(5)
Note: We need to do something to make sure we are accessing the same instance
of TAdditional, that's why I made the private doSomethingAdditional method that takes a TAdditional as an argument. If we call ability and additional in 'doSomething', separate instances of TAdditional would be created when we try to access #ability and #additional, and '0' would be printed.
Suppose I have some abstract value field defined in a trait:
trait Base {
val toBeOverride: String
}
case class Impl(other:Int) extends Base {
override val toBeOverride = "some value"
}
How can I write a function that I can easily get a cloned instance only overriding the toBeOverride value, like this:
// copy only available to case class instance
// v does not have method 'copy'
def overrideBaseValue[T <: Base](v: Base) =
v.copy(toBeOverride = "prefix" + v.toBeOverride)
?
Edit
#som-snytt, I don't think this is a duplicate, just like a Trait is not the same as an Abstract Class. And the answers of that question do not satisfy me, see below.
#Blaisorblade, yes, it is a problem. For instances of each sub case class, the toBeOverride field are the same, so it should not appear in the constructor.
For now all the suggestions are to define an customized copy method in each(!) sub case class and that in my opinion is ugly and shows the incapability of the language.
The simplest solution is to just add the method you want to Base:
trait Base {
val toBeOverride: String
def copyBase(newToBeOverridden: String): Base
}
case class Impl(other:Int, override val toBeOverride: String = "some value") extends Base {
def copyBase(newToBeOverridden: String) = copy(toBeOverride = newToBeOverridden)
}
This also allows to directly create an instance of Impl while specifying the value of toBeOverride (which wasn't possible). The only disadvantage is that now pattern matches using Impl have to change syntax - please update your question and add a comment if that's a problem.
BTW, if you just want to add a prefix (as in your example), that's no problem:
case class Impl(other:Int, override val toBeOverride: String = "some value") extends Base {
def copyBase(newToBeOverridden: String) = copy(toBeOverride = toBeOverride + newToBeOverridden)
}
Here are two mechanisms.
Apparently, in the near future you'll be able to write a macro that can emit the anonymous subclass, but until then, I think this typeclass is not arduous.
Just kicking the tires on Dynamic here.
import scala.language.dynamics
import scala.reflect._
import scala.reflect.runtime.{ currentMirror => cm }
import scala.reflect.runtime.universe._
trait Base {
def m: String
}
case class Impl(p: Int) extends Base {
override val m = "some value"
}
trait Basic extends Dynamic {
protected def m: String
def selectDynamic(f: String): Any =
if ("m" == f) m else reflecting(this, f)
protected def reflecting(b: Basic, f: String) = {
val im = cm.reflect(b)
val member = im.symbol.typeSignature member newTermName(f)
require(member != NoSymbol, s"No such member $f")
(im reflectMethod member.asMethod)()
}
}
case class Implic(p: Int) extends Basic {
override protected val m = "some value"
}
object Test extends App {
implicit class Copy[A <: Base](val b: A) {
def overriding(overm: String): A = (b match {
case impl: Impl => new Impl(impl.p) { override val m = overm }
case b: Base => new Base { override val m = overm }
}).asInstanceOf[A]
}
implicit class Proxy[A <: Basic : ClassTag](val b: A) {
def proximately(overm: String): Basic = new Basic {
override val m = overm
override def selectDynamic(f: String): Any =
if ("m" == f) overm else reflecting(b, f)
override def toString = b.toString
}
}
// asked for this
//def overriding[T <: Base](v: Base) = v.copy(m = "prefix" + v.m)
/* want something like this
def overriding[T <: Base](v: Base) = new Impl(v.p) {
override val m = "some value"
} */
val a = Impl(5)
val b = a overriding "bee good"
Console println s"$a with ${a.m} ~> $b with ${b.m}"
// or
val c = Implic(7)
val d = c proximately "dynomite"
Console println s"$c with ${c.m} ~> $d with ${d.m}"
}
Since traits don't get copy methods automatically, you can try using a Base case class instead:
case class Base(toBeOverride: String)
case class Impl(other: Int, someVal: String = "some value") extends Base(someVal)
def overrideBaseValue[T <: Base](v: Base) =
v.copy(toBeOverride = "prefix" + v.toBeOverride)
The problem that you're going to run into though, is that copy returns an instance of Base and I don't think that you can convert it back to your original Impl class. For instance, this won't compile:
def overrideBaseValue[T <: Base](v: T): T =
v.copy(toBeOverride = "prefix" + v.toBeOverride)