【问题标题】:Is it possible to force early evaluation of a curried function?是否可以强制对咖喱函数进行早期评估?
【发布时间】:2017-07-11 06:01:22
【问题描述】:

考虑一个返回另一个函数的函数:

def prepareFunction(args: List[Any]): String => Unit = {
  println(s"Slow processing of $args...")
  val results = args.map(a => s"processed $a")

  def doSomething(s: String): Unit = {
    println(s"Do something quick with $s and $results")
  }

  doSomething
}

这里的想法是:一个外部函数进行一些繁重的处理并返回一个使用在封闭范围中定义的变量的内部函数:

val doSomethingWithArgs = prepareFunction(List("arg1", "arg2", 3)) 
   //> Slow processing of List(arg1, arg2, 3)...
doSomethingWithArgs("abc")
   //> Do something quick with abc and List(processed arg1, processed arg2, processed 3)
doSomethingWithArgs("cde") 
   //> Do something quick with cde and List(processed arg1, processed arg2, processed 3)

请注意,外部函数只计算一次。

使用多个参数列表和 Scala 的 Currying syntax 我们可以编写类似的内容:

def prepareCurried(args: List[Any])(s: String): Unit = {
  println(s"Slow processing of $args")
  val results = args.map(a => s"processed $a")

  def doSomething(s: String): Unit = {
    println(s"Do something quick with $s and $results")
  }

  doSomething(s)
}  

但“外部”函数每次都会被评估:

val doSomethingWithOtherArgs = prepareCurried(List(4, 5, 6)) _
doSomethingWithOtherArgs("abc")
  //> Slow processing of List(4, 5, 6)
  //> Do something quick with abc and List(processed 4, processed 5, processed 6)
doSomethingWithOtherArgs("cde")
  //> Slow processing of List(4, 5, 6)
  //> Do something quick with cde and List(processed 4, processed 5, processed 6)

我的问题是,我可以以某种方式强制prepareCurried 在下面的行中进行评估吗?

val doSomethingWithOtherArgs = prepareCurried(List(4, 5, 6)) _

换句话说,在部分应用具有多个参数列表的函数时,是否可以获得与"evaluation on definition"相同的效果?

【问题讨论】:

  • 这就是柯里化函数的定义应该如何表现,我想不出你为什么要改变它的任何理由。您的模式 1 中已经有其他选择。
  • 嘿@Sarvesh,没有什么特别的原因,我只是想知道currying和返回函数的函数之间的对称性。

标签: scala currying


【解决方案1】:

在这种情况下,从 Scala 的反射 API 中分离出 reify 有助于了解语法是如何被脱糖的:

scala> import scala.reflect.runtime.universe.{ reify, showCode }
import scala.reflect.runtime.universe.{reify, showCode}

scala> showCode(reify(prepareCurried(List(4, 5, 6)) _).tree)
res0: String =
{
  val eta$0$1 = List.apply(4, 5, 6);
  ((s) => $read.prepareCurried(eta$0$1)(s))
}

如果没有s,根本无法获得preparedCurried(eta) 部分。当您考虑如何在 JVM 上表示具有多个参数列表的方法时,这是有道理的(即所有参数列表只是被粉碎在一起)。正如上面的评论者所说,如果您希望能够将第二个函数的准备与其应用程序分开,您需要显式地返回一个函数。

【讨论】:

  • 有趣。出于好奇,我尝试了showCode(reify(List(1, 2, 3).foldLeft(0) _).tree) 的模拟结果(((op) => eta$0$1.foldLeft(0)(op)))。有道理,所以,我们真正得到的是围绕原始函数的“包装”函数,对吧? prepareCurried _ 实际上是((args) => ((s) => prepareCurried(args)(s)))
  • @AnthonyAccioly 是的,但在 Function1 意义上并没有真正的“原始函数”——prepareCurried _ 只是对匿名函数定义进行脱糖的语法。
【解决方案2】:

我认为如果我们将 prepareFunction 的内部代码写成函数字面量而不是 defs 会更容易看出发生了什么。

这将是您的方法最初的样子:

def prepareFunction(args: List[Any]): String => Unit = {
  println(s"Slow processing of $args...")
  val results = args.map(a => s"processed $a")

  (s: String) => println(s"Do something quick with $s and $results")
}

这与您的咖喱方法“等效”:

def prepareCurried(args: List[Any]): Unit = (s: String) => {
  println(s"Slow processing of $args")
  val results = args.map(a => s"processed $a")

  println(s"Do something quick with $s and $results")
}

现在很容易看出,在prepareCurried 中,缓慢的处理是返回函数的一部分。

对此你无能为力。这就是在使用多个参数列表而不是显式编写函数时失去的灵活性。您必须为每个用例选择更适合的。

【讨论】:

    猜你喜欢
    • 2010-12-27
    • 1970-01-01
    • 1970-01-01
    • 2022-01-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多