The scenario I am trying to model is as follows. I have a couple of case classes that differ in their parameters, but they all extend the trait Entity
.
// case classes
trait Entity
case class E1(..params..) extends Entity
case class E2(..params..) extends Entity
...
case class En(..params..) extends Entity
I have a set of functions that take one parameter which is a subtype of Entity
, like the following (we have more functions than entities):
// functions using case classes as parameters
def f1(val p:E1) = ???
def f2(val p:E4) = ???
...
def fm(val p:E2) = ???
Now, I get an instance of an Entity
serialized into a String, and next to it I get the name of the function to call on it. To deserialize is not a problem: let's say I have a function read[T](str)
that can deserialize str
into an object of type T
.
I want to write a generic piece of Scala code, that given these two Strings (function name, serialized entity) can call the right function after deserializing the entity.
I thought, I would need maps like below that given a function name will give me the function itself, and the type of its parameter. Then I should, in principle, easily be able to make a call as below.
// the mappings from String to entity and corresponding function
val map1 = Map (
"f1" -> f1
"f2" -> f2
...
"fn" -> fn
)
}
val map2 = Map (
"f1" -> E1
"f2" -> E4
...
"fn" -> E2
)
}
def makeTheCall (fname: String, ent: String) = map1.get(fname)(read[map2.get(fname)](ent))
This does not work because I cannot get the types right (and definitely the inferred types do not work either).
Is there a way to put map1
and map2
together (so that there is less chance of messing up the relations between the functions and parameter types)?
EDIT: For the sake of simplicity, we can here ignore the parameters to the Entities and therefore the actual serialized entity. This should help write a compilable code without too much work.
EDIT: Use-case: I am writing a program that receives messages from RabbitMQ. The body of the message contains the entity, and the message key implies what to do with it.
Edit 2: Using a similar function as my previous edit, you could create a map with String => Unit
functions which combine the deserialization and your function, so you don't need two maps.
def deserializeAnd[E <: Entity](f: E => Unit): String => Unit =
(s: String) => f(read[E](s))
val behaviour = Map(
"key1" -> deserializeAnd(println(_: Foo)),
"key2" -> deserializeAnd(println(_: Bar)),
"key3" -> deserializeAnd((foo: Foo) => println(foo.copy(a=0))
)
def processMessage(key: String, serialized: String): Option[Unit] =
behaviour.get(key).map(f => f(serialized))
// throws an exception if 'behaviour' doesn't contain the key
def processMessage2(key: String, serialized: String): Unit =
behaviour(key)(serialized)
Edit: It seems you have potentially multiple functions with the same input type, which makes it not a good use case for a type class.
You could use something like :
def makeTheCall[E <: Entity, Out](f: E => Out, s: String): Out = f(read[E](s))
It deserializes your string to the input type of the passed function.
Which you could use as makeTheCall(f2, "serializedE4")
.
Even if you could find the right types to get your makeTheCall
method working, you shouldn't use strings to differentiate between multiple types. What if you make a typo in fname
, map1
contains fname
and map2
doesn't, ... ?
It is not really clear from your question what you want to do exactly, but it seems a type class would be a good fit for your case. With a type class you can create an instance with specific functionality for a type, which could do something like you want to do with your f1
, f2
, ... functions.
Imagine that your f1
, f2
, ... functions all return an Int
, we could create a type class which contains such a function for Entity
types :
trait EntityOperation[E <: Entity] {
def func(e: E): Int
}
Lets create some case classes which extend Entity
:
trait Entity
case class Foo(a: Int, b: Int) extends Entity
case class Bar(c: String, d: String) extends Entity
Now We can create an instance of our type class for Foo
and Bar
:
implicit val FooEntityOp = new EntityOperation[Foo] {
def func(foo: Foo) : Int = foo.a + foo.b
}
implicit val BarEntityOp = new EntityOperation[Bar] {
def func(bar: Bar) : Int = bar.c.length + bar.d.length
}
We could use our type class as follows :
def callF[E <: Entity](e: E)(implicit op: EntityOperation[E]) = op.func(e)
callF(Foo(1, 2)) // Int = 3
callF(Bar("xx", "yyyy")) // Int = 6
In your case this could look like :
def makeTheCall[E <: Entity](s: String)(implicit op: EntityOperation[E]) =
op.func(read[E](s))
// makeTheCall[Baz]("serializedBaz")