我正在学习函数式编程,通过阅读Paul Chiusano和Rúnar Bjarnason的 Scala函数式编程一书。我特别在第3章,在那里我实现了一些配套函数到一个表示单链表的类,作者提供了。
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可以根据它对List类型的了解,在没有任何注释的情况下确定f的类型,这使得函数更方便使用。
所以,如果我们在相同的参数组中传递f, scala将强制调用变成这样:val total = List.dropWhile(example, (x:Int) => 6%x==0 )
,我们显式定义x的类型,我们将"失去"部分函数应用的可能性,我说的对吗?
然而,为什么在这种情况下部分函数应用程序是有用的?只允许类型推断?"部分应用"像dropWhile这样的函数而不应用谓词f对它有意义吗?因为在我看来,如果我们不应用f…,计算就会在有用之前"停止"。
所以…为什么部分函数应用程序是有用的?这是一直以来的做法,还是Scala特有的做法?我知道Haskell有一种叫做"完全推理"的东西,但我不知道它的确切含义…
Thanks in advance
里面有几个问题,所以我试着分开回答。
关于类型推断,是的,分离参数列表有助于编译器推断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
如果不将两个参数列表分开,编译器就不能在对f
进行类型检查之前"停止"并确定A
的类型。在决定A
的类型时,f
将是"对话"的一部分,因此您需要对其进行注释。
Haskell可以做得更好,因为它使用不同的类型系统(Hindley-Milner),也可以使用从函数应用的上下文派生的信息。这就是为什么它也被称为"完整的"或"通用的"。
为什么Scala没有一个Hindley-Milner类型的系统?长话短说,因为Scala也支持子类型,这与如此强大的类型系统很难共存。关于这个主题的更多信息:
- 为什么Scala'的类型推断不如Haskell'的强大?
- http://www.codecommit.com/blog/scala/what-is-hindley-milner-and-why-is-it-cool
- http://www.scala-lang.org/old/node/4654
关于局部应用,"为什么它有用"这个问题肯定太宽泛了,无法在这里回答。但是,在特定的dropWhile
情况下,假设您有一个表示不同"删除"条件的函数列表。使用部分应用的函数,您可以这样做:
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))
但是柯里化允许在组合函数时更灵活。
关于主题的更多信息:
- https://softwareengineering.stackexchange.com/questions/185585/what-is-the-advantage-of-currying
- 在Scala中,多个参数列表和每个列表多个参数有什么区别?