Reputation: 1159
How can I create a map of Strings to functions with generic types? For example, I want to be able to create a map at compile time:
var unaryFunctionMap: Map[String, Any => Any] = Map[String, Any => Any]()
then I want to add functions that can accept a combination of types of String
, Int
, Double
, etc. Possibly in a way that looks like:
unaryFunctionMap += ("Abs" -> Functions.add)
unaryFunctionMap += ("Neg" -> Functions.neg)
unaryFunctionMap += ("Rev" -> Functions.rev)
Where I implement the actual functions in my Functions
class:
def abs[T: Numeric](value: T)(implicit n: Numeric[T]): T = {
n.abs(value)
}
def neg[T: Numeric](value: T)(implicit n: Numeric[T]): T = {
n.negate(value)
}
def rev(string: String): String = {
string.reverse
}
So, abs
and neg
both permit Numeric
types and rev
only allows for String
s. When I use the map, I want to be able to specify the typing if necessary. Something like this:
(unaryFunctionMap.get("abs").get)[Int](-45)
or, if that isn't possible,
(unaryFunctionMap.get("abs").get)(Int, -45)
How can I modify my code to implement this functionality?
Upvotes: 0
Views: 1817
Reputation: 3541
The problem with storing your functions as Any => Any
is that functions are contravariant in their argument type but covariant in their return type, so an Int => Int
is not a subtype of Any => Any
. You could use the existential type to solve this problem, but this still won't give you type safety.
As a solution, you may want to use a Map[Type, Map[String, _ => _]]
and make sure that every entry put into the map only uses functions of the corresponding type.
The following is more of a sketch than a definite solution; I do not know type tags enough to guarantee correctness or reason about performance (this will need reflection to work).
import scala.reflect.runtime.universe._
class UnaryFunctionMap {
private var internal: Map[Type, Map[String, _ => _]] = Map.empty
def +=[A : TypeTag] (key: String, function: A => A): Unit ={
val a: Map[String, _ => _] = internal.getOrElse(typeOf[A], Map.empty)
// Because of the TypeTag A we make sure that only functions A => A are put into the map where Type == typeOf[A]
internal += typeOf[A] -> (a + (key -> function))
}
def get[A: TypeTag](key: String): Option[A => A] = internal.get(typeOf[A]).flatMap(_.get(key).map(_.asInstanceOf[A => A]))
}
This is potentially unsafe due to the explicit cast in the method get
, so we need to make sure that internal
is filled correctly.
Example usage:
object UnaryFunctionMap {
def main(args: Array[String]) {
val neg: Int => Int = i => -i
val rev: String => String = s => s.reverse
val map = new UnaryFunctionMap
map += ("neg", neg)
map += ("rev", rev)
println(map.get[Int]("neg").map(_.apply(-45))) // Some(45)
println(map.get[String]("neg").map(_.apply("reverto"))) // None
println(map.get[Int]("rev").map(_.apply(-45))) // None
println(map.get[String]("rev").map(_.apply("reverto"))) // Some(otrever)
}
}
Note that I assumed the functions to be of A => A
for arbitrary types of A
. If you want the functions to be A => B
for arbitrary A
and B
you would need to add the second type to the map and update the two methods accordingly.
Upvotes: 2
Reputation: 28511
It sounds like what you want are type classes. For numerics specifically, you don't actually need custom typeclasses as Scala already provided them. If you don't want shared behaviour between all types, you simply augment.
object Test {
// In this case you can probably even specialize to avoid unboxing.
implicit class NumericOps[T : Numeric](val obj: T) extends AnyVal {
def whatever(x: T): T = obj.add(x) // for instance
}
}
Then for all numerics, you get augmentation for free:
import Test._
5.whatever(6)// = 11
Just repeat the same pattern for strings, this is a normal pimp my library pattern. If you want to implement the same operation over a bunch of unrelated types, look at type classes.
Upvotes: 0