我经常面临以下情况:假设我有这三个功能
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)
一方面,这个解决方案具有扁平结构,就像 let
在 Clojure 中,但从另一方面来说,我需要将函数的结果包装在 Option
中并接收Option
结果来自 calculate
(我正在处理空值,这很好,但我不......也不想)。
有更好的方法来实现我的目标吗?处理这种情况的惯用方法是什么(也许我应该继续使用 val
s...但是 let
这样做的方式看起来很优雅)?
从另一方面来说,它连接到 Referential transparency 。所有三个函数都是引用透明的(在我的示例中 firstFn
计算一些常数,如 Pi),因此理论上它们可以用计算结果替换。我知道这一点,但编译器不知道,所以它无法优化我的第一次尝试。这是我的第二个问题:
我可以以某种方式(可能通过注释)向编译器提示我的函数是引用透明的,以便它可以为我优化这个函数(例如,在那里放置某种缓存)?强>
编辑
感谢大家的精彩解答!不可能选出一个最佳答案(可能是因为它们都很好),所以我会接受得票最多的答案,我认为这很公平。
最佳答案
在非递归情况下,let 是 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)}}
当然,这仍然是嵌套的。无法避免这一点。但你说你喜欢单子(monad)形式。这是身份单子(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)
}
这是你的一元计算。通过调用 .x 解开结果
def calculateMonad(a : Long) = for {
first <- Identity(firstFn)
second <- Identity(secondFn(first))
} yield thirdFn(first, second, second + a)
但此时它看起来确实像原始的 val 版本。
Scalaz 中的 Identity monad 更加复杂
http://scalaz.googlecode.com/svn/continuous/latest/browse.sxr/scalaz/Identity.scala.html
关于scala - Clojure 的 'let' 在 Scala 中等效,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4881443/