0

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?

Corey Wu
  • 1,209
  • 1
  • 22
  • 39
  • What are you trying to use this for? This type of `Any => Any` map is often an antipattern to be avoided in Scala. Understanding your use case might help answerers identify a better design pattern to use here. – Ben Reich Jul 13 '15 at 23:06
  • I'd like to create a map of function names to functions. These functions can be ```String => String```, ```Int => String```, ```Double => Double```, etc, so I used ```Any => ```. These functions are also usually ```T: Numeric => T```, so I would like to be able to pass in what type ```T``` is at runtime. – Corey Wu Jul 13 '15 at 23:11
  • That was clear. I'm trying to better understand what you want to use this map for. – Ben Reich Jul 13 '15 at 23:12
  • I am using a parser to parse a user's expression into functions and operands. The map holds all valid functions in the language. – Corey Wu Jul 13 '15 at 23:14
  • I am not sure if this is a better question, but this is more specifically what I am currently trying to achieve: http://stackoverflow.com/questions/31395488/scala-how-can-i-exclude-my-functions-generic-type-until-use – Corey Wu Jul 14 '15 at 00:05

2 Answers2

2

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.

Kulu Limpa
  • 3,501
  • 1
  • 21
  • 31
0

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.

flavian
  • 28,161
  • 11
  • 65
  • 105
  • I'm not sure I understand what you are suggesting. How can I implement this into my function map? Do I need to do something like this: ``` functionMap += ("Neg" -> (value => value.abs()) )```? When I try this, I get a compilation error because ```abs``` is not a member of ```Any```. – Corey Wu Jul 13 '15 at 23:45