Scala 中的偏函数应用

Partial Function Application in Scala

我正在学习函数式编程,方法是阅读 Paul Chiusano 和 Rúnar Bjarnason 合着的 Scala 中的函数式编程 一书。我特别关注第 3 章,在那里我将一些 伴随函数 实现到 class 表示作者提供的单链表。

package fpinscala.datastructures

sealed trait List[+A]
case object Nil extends List[Nothing]
case class Cons[+A](head: A, tail: List[A]) extends List[A]

object List {
    def sum(ints: List[Int]): Int = ints match {
    case Nil => 0
    case Cons(x,xs) => x + sum(xs)
    }

    def product(ds: List[Double]): Double = ds match {
    case Nil => 1.0
    case Cons(0.0, _) => 0.0
    case Cons(x,xs) => x * product(xs)
    }

    def apply[A](as: A*): List[A] =
    if (as.isEmpty) Nil
    else Cons(as.head, apply(as.tail: _*))

    def tail[A](ls: List[A]): List[A] = ls match {
    case Nil => Nil
    case Cons(x,xs) => xs
    }
... (more functions)
}

我正在实现的函数在对象列表中,是配套函数

在实现dropWhile时,其方法签名为:

def dropWhile[A](l: List[A])(f: A => Boolean): List[A]

我遇到了一些关于偏函数应用的问题:

在书中,作者说谓词 f 在单独的参数组中传递,以帮助 Scala 编译器进行类型推断,因为如果我们这样做,Scala 可以在没有任何注释的情况下确定 f 的类型,基于它对 List 类型的了解,这使得函数使用起来更方便。

所以,如果我们在同一个参数组中传递 f,scala 会强制调用变成这样:val total = List.dropWhile(example, (x:Int) => 6%x==0 ) 我们明确定义 x 的类型,我们会 "lose"部分功能应用的可能性,我说得对吗?

但是,为什么部分函数应用程序在这种情况下有用?只允许类型推断? "partially apply" 像 dropWhile 这样的函数而不对其应用谓词 f 是否有意义?因为在我看来,如果我们不应用 f...

,那么在有用之前计算就变成了 "halted"

所以...为什么偏函数应用有用?这是一贯的做法还是只是 Scala 特有的?我知道 Haskell 有一个叫做 "complete inference" 的东西,但我不知道它的确切含义...

提前致谢

里面有几个问题,我会尽量分开回答。

关于类型推断,是的,分离参数列表有助于编译推断 f 的类型。

这是因为 Scala 具有线性局部类型推断(从左到右),它使用第一个参数列表来推断 A(从 l 的类型)。然后它可以使用此信息推断 f.

的类型

举个例子

dropWhile(List(1, 2, 3))(x => x < 3)

编译器将执行以下步骤:

  • 第一个参数列表

    • A 未知。
    • 预期 List[A]
    • 提供List[Int](这是根据List中元素的类型推断的)
    • => A 是一个 Int
  • 第二个参数列表

    • 我们知道A = Int
    • 所以我们期待函数 Int => Boolean 作为 f

如果不将两个参数列表分开,编译器将无法"stop"并在类型检查f之前确定A的类型。 f 在决定​​ A 的类型时将成为 "conversation" 的一部分,因此您需要对其进行注释。

这是 Haskell 可以做得更好的地方,因为它使用不同的类型系统 (Hindley-Milner),该系统也可以使用从应用函数的上下文派生的信息。这就是为什么它也被称为 "complete" 或 "universal".

的原因

为什么 Scala 没有 Hindley-Milner 类型系统?长话短说,因为 Scala 还支持子类型化,这很难与如此强大的类型系统共存。有关该主题的更多信息:

关于部分应用,问题"why is it useful"肯定太宽泛了,这里无法回答。但是,在特定的 dropWhile 情况下,假设您有一个表示不同 "drop" 条件的函数列表。使用部分应用的函数,您可以执行以下操作:

val list = List(1, 2, 3)
val conditions: List[Int => Boolean] = List(_ < 1, _ < 2, _ < 3)
conditions.map(dropWhile(list)) // List(List(1, 2, 3), List(2, 3), List(3))

显然,使用非柯里化函数(即单个参数列表),您可以使用

实现相同的效果
val list = List(1, 2, 3)
val conditions: List[Int => Boolean] = List(_ < 1, _ < 2, _ < 3)
conditions.map(cond => dropWhile(list, cond))

但是柯里化允许在组合函数时更加灵活。

有关该主题的更多信息: