Clojure 的“让” Scala 中的等效项

发布于 2024-10-15 14:49:23 字数 1916 浏览 8 评论 0原文

我经常面临以下情况:假设我有这三个函数

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 函数 我可以在一个表达式中定义此函数。

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

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 的结果(我正在处理空值,这很好,但我不..并不想)。

有更好的方法来实现我的目标吗?处理这种情况的惯用方法是什么(也许我应该继续使用 vals...但是 let 的做法看起来很优雅)?

另一方面,它与引用透明度相关。所有三个函数都是引用透明的(在我的示例中,firstFn 计算一些常数,如 Pi),因此理论上它们可以用计算结果替换。我知道这一点,但编译器不知道,所以它无法优化我的第一次尝试。这是我的第二个问题:

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

编辑

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

Often I face following situation: suppose I have these three functions

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

and I also have calculate function. My first approach can look like this:

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

It looks beautiful and without any curly brackets - just one expression. But it's not optimal, so I end up with this code:

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

Now it's several expressions surrounded with curly brackets. At such moments I envy Clojure a little bit. With let function I can define this function in one expression.

So my goal here is to define calculate function with one expression. I come up with 2 solutions.

1 - With scalaz I can define it like this (are there better ways to do this with scalaz?):

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

What I don't like about this solution is that it's nested. The more vals I have the deeper this nesting is.

2 - With for comprehension I can achieve something similar:

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

From one hand this solution has flat structure, just like let in Clojure, but from the other hand I need to wrap functions' results in Option and receive Option as result from calculate (it's good it I'm dealing with nulls, but I don't... and don't want to).

Are there better ways to achieve my goal? What is the idiomatic way for dealing with such situations (may be I should stay with vals... but let way of doing it looks so elegant)?

From other hand it's connected to Referential transparency. All three functions are referentially transparent (in my example firstFn calculates some constant like Pi), so theoretically they can be replaced with calculation results. I know this, but compiler does not, so it can't optimize my first attempt. And here is my second question:

Can I somehow (may be with annotation) give hint to compiler, that my function is referentially transparent, so that it can optimize this function for me (put some kind of caching there, for example)?

Edit

Thanks everybody for the great answers! It's just impossible to select one best answer (may be because they all so good) so I will accept answer with the most up-votes, I think it's fair enough.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

白况 2024-10-22 14:49:23

在非递归情况下,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)}}

当然,这仍然是嵌套的。无法避免这一点。但你说你喜欢单子形式。这是恒等单子

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

in the non-recursive case, let is a restructuring of 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)}}

Of course, that's still nested. Can't avoid that. But you said you like the monadic form. So here's the identity 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)
}

And here's your monadic calculate. Unwrap the result by calling .x

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

But at this point it sure looks like the original val version.

The Identity monad exists in Scalaz with more sophistication

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

心房的律动 2024-10-22 14:49:23

坚持原始形式:

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

  thirdFn(first, second, second + a)
}

即使对于 Java 开发人员来说,它也简洁明了。它大致相当于 let,只是不限制名称的范围。

Stick with the original form:

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

  thirdFn(first, second, second + a)
}

It's concise and clear, even to Java developers. It's roughly equivalent to let, just without limiting the scope of the names.

悲念泪 2024-10-22 14:49:23

这是您可能忽略的一个选项。

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))

但现在您可能需要多次命名相同的内容。

Here's an option you may have overlooked.

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

If you actually want to create a method, this is the way I'd do it.

Alternatively, you could create a method (one might name it let) that avoids nesting:

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))

But now you might need to name the same things multiple times.

韶华倾负 2024-10-22 14:49:23

如果您觉得第一种形式更干净/更优雅/更具可读性,那么为什么不坚持使用它呢?

首先,阅读 Martin Odersky 向 Scala 编译器发送的这条最新提交消息并牢记在心......


也许这里真正的问题是立即声称它不是最佳的。 JVM 非常擅长优化这类事情。有时,这简直太神奇了!

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

然后,也只有到那时,您才应该寻找使其更快的方法,但最终可能会牺牲代码的清晰度。

If you feel the first form is cleaner/more elegant/more readable, then why not just stick with it?

First, read this recent commit message to the Scala compiler from none other than Martin Odersky and take it to heart...


Perhaps the real issue here is instantly jumping the gun on claiming it's sub-optimal. The JVM is pretty hot at optimising this sort of thing. At times, it's just plain amazing!

Assuming you have a genuine performance issue in an application that's in genuine need of a speed up, you should start with a profiler report proving that this is a significant bottleneck, on a suitably configured and warmed up JVM.

Then, and only then, should you look at ways to make it faster that may end up sacrificing code clarity.

瞳孔里扚悲伤 2024-10-22 14:49:23

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

defcalculate(a:Long)=firstFnmatch{casef=> SecondFn(f) 匹配 { case s =>第三个Fn(f,s,s + a) } }

Why not use pattern matching here:

def calculate(a: Long) = firstFn match { case f => secondFn(f) match { case s => thirdFn(f,s,s + a) } }

月牙弯弯 2024-10-22 14:49:23

如何使用柯里化来记录函数返回值(前面参数组中的参数在后续组中可用)。

看起来有点奇怪但相当简洁并且没有重复调用:

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

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

How about using currying to record the function return values (parameters from preceding parameter groups are available in suceeding groups).

A bit odd looking but fairly concise and no repeated invocations:

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

println(calculate(1L)()())
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文