我想定义一个函数f
,它接受另一个函数g
。我们要求g
采取n
双打(对于某些固定的n
)并返回双精度。函数调用f(g)
应返回n
的具体值。
例如,f(Math.max) = 2
因为Math.sin的类型为(Double, Double) => Double
,而f(Math.sin) = 1
因为Math.sin的类型为Double => Double
。
如何使用Scala泛型定义f
?
我尝试了几种形式而没有成功。例如:
def f[A <: Product](g: Product => Double) = {...}
这不起作用,因为我们无法在编译时提取n
的值,并且不能将A
限制为仅包含Double
值。
答案 0 :(得分:7)
Magnet Pattern团队创建了一个名为Spray的模式。它完全符合您的要求
答案 1 :(得分:5)
这是我调查Shapeless的一个很好的借口,这是我一直想做的事情:)
$ git clone git@github.com:milessabin/shapeless.git
...
$ cd shapeless
(1)
Shapeless提供了一些关于arity的抽象,特别是表示为异构列表(HList
)。任意arity的函数可以看作FnHList
(一个以HList
为参数的函数。)
$ sbt shapeless-core/console
scala> import shapeless._
import shapeless._
scala> def isFunction[A](fun: A)(implicit fnh: FnHLister[A]) {}
isFunction: [A](fun: A)(implicit fnh: shapeless.FnHLister[A])Unit
scala> isFunction(math.sqrt _)
scala> isFunction(math.random _)
(2)
现在让我们要求函数返回Double
:
scala> def isFunReturningDouble[A](fun: A)(implicit fnh: FnHLister[A] { type Result = Double }) {}
isFunReturningDouble: [A](fun: A)(implicit fnh: shapeless.FnHLister[A]{type Result = Double})Unit
scala> isFunReturningDouble(math.sqrt _)
scala> isFunReturningDouble(math.signum _)
<console>:12: error: could not find implicit value for parameter fnh: shapeless.FnHLister[Int => Int]{type Result = Double}
isFunReturningDouble(math.signum _)
^
(3)
LUBConstraint
类型类可以见证参数列表的上限:
scala> def isValidFun[A, B <: HList](fun: A)(implicit fnh: FnHLister[A] { type Result = Double; type Args = B }, lub: LUBConstraint[B, Double]) {}
isValidFun: [A, B <: shapeless.HList](fun: A)(implicit fnh: shapeless.FnHLister[A]{type Result = Double; type Args = B}, implicit lub: shapeless.LUBConstraint[B,Double])Unit
scala> isValidFun(math.random _)
scala> isValidFun((i: Int) => i.toDouble)
<console>:12: error: could not find implicit value for parameter lub: shapeless.LUBConstraint[B,Double]
isValidFun((i: Int) => i.toDouble)
^
(4)
现在我们仍然需要以某种方式提取arity。在类型级别,这将是为Length
提供的HList
。要获取运行时值,需要另一个类型类ToInt
。
以下是最终功能:
import shapeless._
def doubleFunArity[A, B <: HList, C <: Nat](fun: A)(implicit
fnh: FnHLister[A] { type Result = Double; type Args = B },
lub: LUBConstraint[B, Double],
len: Length[B] { type Out = C },
res: ToInt[C]
): Int = res()
测试:
scala> doubleFunArity(math.sqrt _)
res15: Int = 1
scala> doubleFunArity(math.random _)
res16: Int = 0
scala> val g: (Double, Double) => Double = math.max _
g: (Double, Double) => Double = <function2>
scala> doubleFunArity(g)
res17: Int = 2
请注意,遗憾的是,许多math
操作都被重载,并且没有强类型约束,Scala不会自动为您提供Double
版本,但出于某种原因会使用Int
版本:
scala> math.max _
res18: (Int, Int) => Int = <function2>
所以我需要间接math.max _: ((Double, Double) => Double)
来完成这项工作。
并不是说这是在你的具体案例中做到这一点的最佳方式,但我认为这是一次有趣的探索。
答案 2 :(得分:2)
可能最简单的解决方案是使用重载
def f(g: () => Double) = 0;
def f(g: (Double) => Double) = 1;
def f(g: (Double, Double) => Double) = 2;
def f(g: (Double, Double, Double) => Double) = 2;
// ...
println(f(Math.pow _));
println(f(Math.sin _));
(由于类型擦除,您无法在运行时检查函数参数/返回类型,因此我相信您无法创建满足您要求的完全通用函数。)