Clojure在Scala中的'let'等价物

时间:2011-02-03 00:11:21

标签: scala clojure scalaz let

我常常遇到以下情况:假设我有这三个功能

def firstFn: Int = ...
def secondFn(b: Int): Long = ...
def thirdFn(x: Int, y: Long, z: Long): Long = ...

我也有calculate功能。我的第一种方法看起来像这样:

def calculate(a: Long) = thirdFn(firstFn, secondFn(firstFn), secondFn(firstFn) + a)

它看起来很漂亮,没有任何花括号 - 只有一个表达式。但它不是最佳的,所以我最终得到了这段代码:

def calculate(a: Long) = {
  val first = firstFn
  val second = secondFn(first)

  thirdFn(first, second, second + a)
}

现在有几个用大括号括起来的表达式。在这样的时刻,我羡慕Clojure一点点。使用let function,我可以在一个表达式中定义此函数。

所以我的目标是用一个表达式定义calculate函数。我想出了两个解决方案。

1 - 使用 scalaz 我可以像这样定义它(使用scalaz有更好的方法吗?):

  def calculate(a: Long) = 
    firstFn |> {first => secondFn(first) |> {second => thirdFn(first, second, second + a)}}

我不喜欢这个解决方案,它是嵌套的。 val越多,我的嵌套就越深。

2 - 通过for理解,我可以实现类似的目标:

  def calculate(a: Long) = 
    for (first <- Option(firstFn); second <- Option(secondFn(first))) yield thirdFn(first, second, second + a)

一方面,这个解决方案具有扁平结构,就像Clojure中的let一样,但另一方面,我需要将函数的结果包装在Option中并接收Option作为结果。 calculate(我很好处理空值,但我不......并且不想这样做。)

有没有更好的方法来实现我的目标?处理这种情况的惯用方法是什么(可能我应该留在val s ...但let这样做的方式看起来很优雅?

另一方面它连接到Referential transparency。所有这三个函数都是引用透明的(在我的例子中firstFn计算一些像Pi一样的常量),所以理论上它们可以用计算结果代替。我知道这一点,但编译器没有,所以它无法优化我的第一次尝试。这是我的第二个问题:

我可以以某种方式(可能带注释)给编译器提示,我的函数是引用透明的,以便它可以为我优化这个函数(例如在那里放一些缓存)?

修改

感谢大家的精彩回答!选择一个最佳答案是不可能的(可能是因为它们都很好)所以我会接受最高票的答案,我认为这是公平的。

6 个答案:

答案 0 :(得分:12)

在非递归的情况下,让我们重新组织lambda。

def firstFn : Int = 42
def secondFn(b : Int) : Long = 42
def thirdFn(x : Int, y : Long, z : Long) : Long = x + y + z

def let[A, B](x : A)(f : A => B) : B = f(x)

def calculate(a: Long) = let(firstFn){first => let(secondFn(first)){second => thirdFn(first, second, second + a)}}

当然,那仍然是嵌套的。无法避免。但是你说你喜欢monadic形式。所以这是身份monad

case class Identity[A](x : A) {
   def map[B](f : A => B) = Identity(f(x))
   def flatMap[B](f : A => Identity[B]) = f(x)
}

这是你的monadic计算。通过调用.x

打开结果
def calculateMonad(a : Long) = for {
   first <- Identity(firstFn)
   second <- Identity(secondFn(first))
} yield thirdFn(first, second, second + a)

但在这一点上,它确实看起来像原始的val版本。

Identity monad存在于Scalaz中,具有更高级的复杂性

http://scalaz.googlecode.com/svn/continuous/latest/browse.sxr/scalaz/Identity.scala.html

答案 1 :(得分:7)

坚持原始形式:

def calculate(a: Long) = {
  val first = firstFn
  val second = secondFn(first)

  thirdFn(first, second, second + a)
}

它简洁明了,甚至对Java开发人员也是如此。它大致相当于let,只是不限制名称的范围。

答案 2 :(得分:4)

这是您可能忽略的选项。

def calculate(a: Long)(i: Int = firstFn)(j: Long = secondFn(i)) = thirdFn(i,j,j+a)

如果你真的想创建一个方法,这就是我的方法。

或者,您可以创建一个避免嵌套的方法(可以将其命名为let):

class Usable[A](a: A) {
  def use[B](f: A=>B) = f(a)
  def reuse[B,C](f: A=>B)(g: (A,B)=>C) = g(a,f(a))
  // Could add more
}
implicit def use_anything[A](a: A) = new Usable(a)

def calculate(a: Long) =
  firstFn.reuse(secondFn)((first, second) => thirdFn(first,second,second+a))

但现在你可能需要多次命名相同的东西。

答案 3 :(得分:3)

如果您觉得第一种形式更干净/更优雅/更易读,那么为什么不坚持下去呢?

首先,阅读来自Martin Odersky的Scala编译器的this recent commit message并将其铭记于心......


也许这里真正的问题是立即跳起来声称它是次优的。 JVM在优化这类事情方面非常热门。有时候,这简直太神奇了!

假设您在真正需要加速的应用程序中存在真正的性能问题,您应该从分析器报告证明开始,这是一个重要的瓶颈,在适当配置和加热JVM。

然后,只有这样,你是否应该考虑如何让它更快,最终可能会牺牲代码清晰度。

答案 4 :(得分:3)

为什么不在这里使用模式匹配:

def calculate(a:Long)= firstFn match {case f =&gt; secondFn(f)匹配{case s =&gt; thirdFn(f,s,s + a)}}

答案 5 :(得分:0)

如何使用currying来记录函数返回值(前面的参数组中的参数在上级组中可用)。

有点奇怪,但相当简洁,没有重复的调用:

def calculate(a: Long)(f: Int = firstFn)(s: Long = secondFn(f)) = thirdFn(f, s, s + a)

println(calculate(1L)()())