繁体   English   中英

Clojure在Scala中的'let'等价物

[英]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))

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

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

首先,从Martin Odersky中读取最近提交给Scala编译器的提交消息 ,并将其铭记于心......


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

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

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

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

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM