【问题标题】:Generic scala function whose input is a function of variable arity通用 scala 函数,其输入是变量 arity 的函数
【发布时间】:2013-06-19 08:36:35
【问题描述】:

我想定义一个函数f,它接受另一个函数g。我们要求g 接受n Doubles(对于一些固定的n)并返回一个Double。函数调用f(g)应该返回n的具体值。

例如,f(Math.max) = 2,因为 Math.sin 的类型为 (Double, Double) => Doublef(Math.sin) = 1,因为 Math.sin 的类型为 Double => Double

如何使用 Scala 泛型定义 f

我尝试了几种形式都没有成功。例如:

def f[A <: Product](g: Product => Double) = {...}

这不起作用,因为我们无法在编译时提取 n 的值,也无法将 A 限制为仅包含 Double 值。

【问题讨论】:

  • 您说g 采用n 元组,但您的示例Math.max 是n 元函数,而不是采用元组的Function1。你应该澄清这一点。

标签: scala generics


【解决方案1】:

有一个名为Magnet Pattern 的模式,由Spray 团队创建。它完全符合您的要求

【讨论】:

    【解决方案2】:

    这是我调查Shapeless 的一个很好的借口,这是我一直想做的事情:)

    $ git clone git@github.com:milessabin/shapeless.git
    ...
    $ cd shapeless
    

    (1)

    Shapeless 提供了一些对 arity 的抽象,尤其是作为异构列表的表示 (HList)。任意数量的函数可以看作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。在类型级别上,这将是为HList 提供的Length。要获取运行时值,需要另一个类型类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) =&gt; Double) 来完成这项工作。


    并不是说在你的具体情况下这是最好的方法,但我认为这是一次有趣的探索。

    【讨论】:

    • P.S. math.max(_: Double, _: Double) 也可以
    【解决方案3】:

    可能最简单的解决方案是使用重载作为

    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 _));
    

    (由于类型擦除,您无法在运行时检查函数参数/返回类型,所以我相信您无法创建一个完全通用的函数来满足您的要求。)

    【讨论】:

      猜你喜欢
      • 2015-10-07
      • 1970-01-01
      • 2015-05-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多