[英]Clojure's 'let' equivalent in Scala
我经常遇到以下情况:假设我有这三个功能
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函数我可以在一个表达式中定义此函数。
所以我的目标是用一个表达式定义calculate
函数 。 我想出了两个解决方案。
1 - 使用scalaz我可以像这样定义它(有更好的方法用scalaz做到这一点吗?):
def calculate(a: Long) =
firstFn |> {first => secondFn(first) |> {second => thirdFn(first, second, second + a)}}
我不喜欢这个解决方案是它嵌套了。 更val
的I有这样的嵌套越深。
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
...但let
看起来如此优雅)?
另一方面,它与参考透明度相关联 。 所有三个函数都是引用透明的(在我的例子中, firstFn
像Pi一样计算一些常量),所以理论上它们可以用计算结果代替。 我知道这一点,但编译器没有,所以它无法优化我的第一次尝试。 这是我的第二个问题:
我可以以某种方式(可能有注释)给编译器提示,我的函数是引用透明的,以便它可以为我优化这个函数(例如,在那里放置某种缓存)?
谢谢大家的好消息! 选择一个最佳答案是不可能的(可能是因为它们都很好)所以我会接受最多投票的答案,我认为这是公平的。
在非递归的情况下,让我们重组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
坚持原始形式:
def calculate(a: Long) = {
val first = firstFn
val second = secondFn(first)
thirdFn(first, second, second + a)
}
它简洁明了,甚至对Java开发人员也是如此。 它大致等同于let,只是不限制名称的范围。
这是您可能忽略的选项。
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))
但现在你可能需要多次命名相同的东西。
为什么不在这里使用模式匹配:
def calculate(a:Long)= firstFn match {case f => secondFn(f)match {case s => thirdFn(f,s,s + a)}}
如何使用currying来记录函数返回值(前面的参数组中的参数在上升组中可用)。
有点奇怪但相当简洁,没有重复的调用:
def calculate(a: Long)(f: Int = firstFn)(s: Long = secondFn(f)) = thirdFn(f, s, s + a)
println(calculate(1L)()())
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.