【问题标题】:Passing functions for all applicable types around传递所有适用类型的函数
【发布时间】:2011-01-04 08:33:43
【问题描述】:

我按照here 的建议定义了一个名为 square 的函数,然后尝试将它传递给一个调用了两次的函数。函数定义如下:

 def square[T](n: T)(implicit numeric: Numeric[T]): T = numeric.times(n, n)
 def twice[T](f: (T) => T, a: T): T = f(f(a)) 

调用两次(square, 2) 时,REPL 吐出错误信息:

scala> twice(square, 2)
<console>:8: error: could not find implicit value for parameter numeric: Numeric[T]
       twice(square, 2)
         ^

有人吗?

【问题讨论】:

    标签: scala


    【解决方案1】:

    除了Andrew Phillips,我不同意这里的所有人。好了,到此为止。 :-) 问题就在这里:

    def twice[T](f: (T) => T, a: T): T = f(f(a))
    

    您希望 Scala 的编译器像 Scala 的新手一样,考虑到 twiceboth 参数来推断正确的类型。然而,Scala 并没有这样做——它只使用从一个参数列表到下一个参数列表的信息,而不是从一个参数到下一个参数列表。这意味着参数fa 是独立分析的,没有优势知道另一个是什么。

    这意味着,例如,this 有效:

    twice(square[Int], 2)
    

    现在,如果你把它分成两个参数列表,那么它也可以工作:

    def twice[T](a: T)(f: (T) => T): T = f(f(a))
    twice(2)(square)
    

    因此,基本上,您尝试做的所有事情都是正确的并且应该可以工作,除了您期望一个参数来帮助确定另一个参数的类型(正如您所写的那样) )。

    【讨论】:

    • +1。 Nice:“它只使用从一个参数列表到下一个参数列表的信息,而不是从一个参数到下一个参数”
    • 感谢您的明确解释。所以类型推断确实与列表中的参数无关......但是,呃,为什么......? ;-) 我应该补充一点,这不是一个完全严肃的问题,因为我怀疑这里可能潜伏着一个论文长度的答案。
    • @Andrew 我猜是性能。推断一种类型已经够难了——这里需要统一两种类型,然后得出它的推断。这就是具有完整类型推断的语言所做的事情,但是,根据 Odersky 的说法,使用具有与 Scala 相同类型系统的语言来正确地做到这一点是一个难题。
    【解决方案2】:

    这是来自 Scala REPL 的一个会话。

    Welcome to Scala version 2.8.0.final (Java HotSpot(TM) 64-Bit Server VM, Java 1.6.0_20).
    Type in expressions to have them evaluated.
    Type :help for more information.
    
    scala> def square[T : Numeric](n: T) = implicitly[Numeric[T]].times(n, n)
    square: [T](n: T)(implicit evidence$1: Numeric[T])T
    
    scala> def twice2[T](f: T => T)(a: T) = f(f(a))
    twice2: [T](f: (T) => T)(a: T)T
    
    scala> twice2(square)(3)
    <console>:8: error: could not find implicit value for evidence parameter of type
     Numeric[T]
           twice2(square)(3)
                  ^
    
    scala> def twice3[T](a: T, f: T => T) = f(f(a))
    twice3: [T](a: T,f: (T) => T)T
    
    scala> twice3(3, square)
    <console>:8: error: could not find implicit value for evidence parameter of type
     Numeric[T]
           twice3(3, square)
    
    scala> def twice[T](a: T)(f: T => T) = f(f(a))
    twice: [T](a: T)(f: (T) => T)T
    
    scala> twice(3)(square)
    res0: Int = 81
    

    显然,在解析隐式之前需要知道“twice(3)”的类型。我想这是有道理的,但如果 Scala 大师可以对此发表评论,我仍然会很高兴......

    【讨论】:

    • @Scala 大师:今晚我们将与一些 Clojure 同事进行“最优雅的语言”枪战,当然,Scala 必须获胜!所以任何好的解释都非常感谢;-)
    • 看我的回答。基本上,您希望类型在不被推断的地方被推断出来。
    • @Daniel 实际上为整个事情提供了解释。
    【解决方案3】:

    另一种解决方案是将 square 提升为部分应用函数:

    scala> twice(square(_:Int),2)
    res1: Int = 16
    

    通过这种方式,隐式应用于正方形,如下所示:

    scala> twice(square(_:Int)(implicitly[Numeric[Int]]),2)
    res3: Int = 16
    

    还有另一种方法:

    def twice[T:Numeric](f: (T) => T, a: T): T = f(f(a))
    scala> twice[Int](square,2)
    res1: Int = 16
    

    但同样,类型参数没有被推断出来。

    【讨论】:

    • 这可能是更好的方法。但我不喜欢的是我知道明确需要指定一个类型,似乎可以推断出类型。
    【解决方案4】:

    您的问题是 square 不是函数(即 scala.Function1[T, T] aka (T) => T)。相反,它是一个类型参数化的方法,具有多个参数列表,其中一个是隐式的……Scala 中没有语法来定义完全等效的函数。

    有趣的是,您对 Numeric 类型类的使用意味着 Scala 中高级函数的通常编码不直接适用于此,但我们可以将它们调整到这种情况并得到类似的东西,

    trait HigherRankedNumericFunction {
      def apply[T : Numeric](t : T) : T
    }
    
    val square = new HigherRankedNumericFunction {
      def apply[T : Numeric](t : T) : T = implicitly[Numeric[T]].times(t, t)
    }
    

    这给了我们一个更高级别的“函数”,它的类型参数上下文绑定到数值,

    scala> square(2)
    res0: Int = 4
    
    scala> square(2.0)
    res1: Double = 4.0
    
    scala> square("foo")
    <console>:8: error: could not find implicit value for evidence parameter of type Numeric[java.lang.String]
       square("foo")
    

    我们现在可以用 HigherRankedNumericFunctions 定义 两次

    def twice[T : Numeric](f : HigherRankedNumericFunction, a : T) : T = f(f(a))
    
    scala> twice(square, 2)
    res2: Int = 16
    
    scala> twice(square, 2.0)
    res3: Double = 16.0
    

    这种方法的明显缺点是您失去了 Scala 的单态函数字面量的简洁性。

    【讨论】:

    • 好的,我知道了,但我还不满意。我不想只为数字时间定义两次。
    • Miles,我们可以更进一步,定义一个trait HigherKindedImplicitFunction[Kind[_]] { def apply[T: Kind](t: T): T },可能将其推广到更多参数(不需要隐式的类型可以得到一个Id[_]),对吧?这样就不需要twice 需要Numeric[T] 的实例,而是需要Kind[T],即squareNumeric
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-07-17
    • 2016-08-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-02-27
    • 1970-01-01
    相关资源
    最近更新 更多