Corey Wu
Corey Wu

Reputation: 1159

Scala - Map of functions with generic types

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 Strings. 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

Answers (2)

Kulu Limpa
Kulu Limpa

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

flavian
flavian

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

Related Questions