部分应用/ curried函数vs重载函数

时间:2016-03-18 01:47:52

标签: scala

虽然我理解部分应用/ curried函数是什么,但我仍然不完全理解为什么我会使用这样的函数而不是简单地重载函数。即给出:

def add(a: Int, b: Int): Int = a + b
val addV = (a: Int, b: Int) => a + b

之间有什么实际区别
def addOne(b: Int): Int = add(1, b)

def addOnePA = add(1, _:Int)
// or currying
val addOneC = addV.curried(1)

请注意我不是在询问currying和部分应用的函数,因为之前已经提出过这个问题并且我已经阅读了答案。我问的是关于curried /部分应用的函数VS重载函数

2 个答案:

答案 0 :(得分:1)

您的示例的不同之处在于,重载函数将为1的第一个参数设置硬编码值add,即在编译时设置,而部分应用或curried函数用于捕获其参数动态地,即在运行时。否则,在您的特定示例中,因为您在两种情况下都对1进行了硬编码,所以它几乎是一样的。

当您通过不同的上下文传递它时,您将使用部分应用/ curried函数,并且它会动态捕获/填充参数,直到它完全准备好进行评估。在FP中这很重要,因为很多时候你没有传递值,而是传递函数。它允许更高的可组合性和代码可重用性。

答案 1 :(得分:1)

为什么您可能更喜欢部分应用功能,这有几个原因。最明显的,也许是肤浅的一个是你不必写出addOnePA等中间函数。

List(1, 2, 3, 4) map (_ + 3) // List(4, 5, 6, 7)

更好
def add3(x: Int): Int = x + 3
List(1, 2, 3, 4) map add3

即使是匿名函数方法(下划线最终扩展到编译器)也比较笨拙。

List(1, 2, 3, 4) map (x => x + 3)

从表面上看,当您真正将函数作为一等值传递时,部分应用程序会派上用场。

val fs = List[(Int, Int) => Int](_ + _, _ * _, _ / _)
val on3 = fs map (f => f(_, 3)) // partial application
val allTogether = on3.foldLeft{identity[Int] _}{_ compose _}

allTogether(6) // (6 / 3) * 3 + 3 = 9

想象一下,如果我没有告诉你fs中的功能是什么。提出命名函数等价而不是部分应用的技巧变得更难使用。

至于currying,currying函数通常可以让你自然地表达产生其他函数的函数的转换(而不是在最后简单地产生非函数值的高阶函数),否则可能不太清楚。

例如,

def integrate(f: Double => Double, delta: Double = 0.01)(x: Double): Double = {
  val domain = Range.Double(0.0, x, delta)
  domain.foldLeft(0.0){case (acc, a) => delta * f(a) + acc      
}

可以被思考并用于实际学习微积分中的积分的方式,即作为产生另一个函数的函数的变换。

def square(x: Double): Double = x * x

// Ignoring issues of numerical stability for the moment...
// The underscore is really just a wart that Scala requires to bind it to a val
val cubic = integrate(square) _ 
val quartic = integrate(cubic) _
val quintic = integrate(quartic) _

// Not *utterly* horrible for a two line numerical integration function
cubic(1) // 0.32835000000000014
quartic(1) // 0.0800415
quintic(1) // 0.015449626499999999

Currying还缓解了固定功能的一些问题。

implicit class LiftedApply[A, B](fOpt: Option[A => B]){ 
  def ap(xOpt: Option[A]): Option[B] = for {
    f <- fOpt
    x <- xOpt
  } yield f(x)
}

def not(x: Boolean): Boolean = !x
def and(x: Boolean)(y: Boolean): Boolean = x && y
def and3(x: Boolean)(y: Boolean)(z: Boolean): Boolean = x && y && z

Some(not _) ap Some(false) // true
Some(and _) ap Some(true) ap Some(true) // true
Some(and3 _) ap Some(true) ap Some(true) ap Some(true) // true

通过使用curried函数,我们已经能够“提升”一个函数来处理Option我们需要的多个参数。如果我们的逻辑函数没有被计算,那么我们必须有单独的函数来将A => B提升到Option[A] => Option[B](A, B) => C(Option[A], Option[B]) => Option[C](A, B, C) => D (Option[A], Option[B], Option[C]) => Option[D]以及我们关心的所有神灵等等。

当涉及到类型推断时,Currying还有一些其他的好处,如果你有一个方法的implicit和非 - implicit参数,则需要它。

Finally, the answers to this question列出了你可能想要讨论的次数。