简体   繁体   中英

Scala - How can I exclude my function's generic type until use?

I have a map of String to Function s which details all of the valid functions that are in a language. When I add a function to my map, I am required to specify the type (in this case Int ).

var functionMap: Map[String, (Nothing) => Any] = Map[String, (Nothing) => Any]()

functionMap += ("Neg" -> expr_neg[Int])

def expr_neg[T: Numeric](value: T)(implicit n: Numeric[T]): T = {
  n.negate(value)
} 

Instead, how can I do something like:

functionMap += ("Neg" -> expr_neg)

without the [Int] and add it in later on when I call:

(unaryFunctionMap.get("abs").get)[Int](-45)

You're trying to build your function using type classes (in this case, Numeric ). Type classes rely on implicit parameters. Implicits are resolved at compile time. Your function name string values are only known at runtime, therefore you shouldn't build your solution on top of type classes like this.

An alternative would be to store a separate function object in your map for each parameter type. You could store the parameter type with a TypeTag :

import scala.reflect.runtime.universe._

var functionMap: Map[(String, TypeTag[_]), (Nothing) => Any] = Map()

def addFn[T: TypeTag](name: String, f: T => Any) =
  functionMap += ((name, typeTag[T]) -> f)

def callFn[T: TypeTag](name: String, value: T): Any =
  functionMap((name, typeTag[T])).asInstanceOf[T => Any](value)

addFn[Int]("Neg", expr_neg)
addFn[Long]("Neg", expr_neg)
addFn[Double]("Neg", expr_neg)

val neg10 = callFn("Neg", 10)

No type class implicit needs to be resolved to call callFn() , because the implicit Numeric was already resolved on the call to addFn .


What happens if we try to resolve the type class when the function is called?

The first problem is that a Function1 (or Function2 ) can't have implicit parameters. Only a method can. (See this other question for more explanation.) So if you want something that acts like a Function1 but takes an implicit parameter, you'll need to create your own type that defines the apply() method. It has to be a different type from Function1 , though.

Now we get to the main problem: all implicits must be able to be resolved at compile time. At the location in code where the method is run, all the type information needed to choose the implicit value needs to be available. In the following code example:

unaryFunctionMap("abs")(-45)

We don't really need to specify that our value type is Int , because it can be inferred from the value -45 itself. But the fact that our method uses a Numeric implicit value can't be inferred from anything in that line of code. We need to specify the use of Numeric somewhere at compile time.

If you can have a separate map for unary functions that take a numeric value, this is (relatively) easy:

trait UnaryNumericFn {
  def apply[T](value: T)(implicit n: Numeric[T]): Any
}

var unaryNumericFnMap: Map[String, UnaryNumericFn] = Map()

object expr_neg extends UnaryNumericFn {
  override def apply[T](value: T)(implicit n: Numeric[T]): T = n.negate(value)
}

unaryNumericFnMap += ("Neg" -> expr_neg)

val neg3 = unaryNumericFnMap("Neg")(3)

You can make the function trait generic on the type class it requires, letting your map hold unary functions that use different type classes. This requires a cast internally, and moves the specification of Numeric to where the function is finally called:

trait UnaryFn[-E[X]] {
  def apply[T](value: T)(implicit ev: E[T]): Any
}

object expr_neg extends UnaryFn[Numeric] {
  override def apply[T](value: T)(implicit n: Numeric[T]): T = n.negate(value)
}

var privateMap: Map[String, UnaryFn[Nothing]] = Map()

def putUnary[E[X]](key: String, value: UnaryFn[E]): Unit =
  privateMap += (key -> value)

def getUnary[E[X]](key: String): UnaryFn[E] =
  privateMap(key).asInstanceOf[UnaryFn[E]]

putUnary("Neg", expr_neg)

val pos5 = getUnary[Numeric]("Neg")(-5)

But you still have to specify Numeric somewhere.

Also, neither of these solutions, as written, support functions that don't need type classes. Being forced to be this explicit about which functions take implicit parameters, and what kinds of implicits they use, starts to defeat the purpose of using implicits in the first place.

You can't. Because expr_neg is a method with a type parameter T and an implicit argument n depending on that parameter. For Scala to lift that method to a function, it needs to capture the implicit, and therefore it must know what kind of type you want.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM