Clojure 的“让” 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 函数 我可以在一个表达式中定义此函数。
所以我的目标是用一个表达式定义计算
函数。我想出了两个解决方案。
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
的做法看起来很优雅)?
另一方面,它与引用透明度相关。所有三个函数都是引用透明的(在我的示例中,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 val
s 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 val
s... 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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
在非递归情况下,let 是 lambda 的重组。
当然,这仍然是嵌套的。无法避免这一点。但你说你喜欢单子形式。这是恒等单子
这是你的单子计算。通过调用 .x 解开结果
,但此时它看起来确实像原始的 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.
Of course, that's still nested. Can't avoid that. But you said you like the monadic form. So here's the identity monad
And here's your monadic calculate. Unwrap the result by calling .x
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
坚持原始形式:
即使对于 Java 开发人员来说,它也简洁明了。它大致相当于 let,只是不限制名称的范围。
Stick with the original form:
It's concise and clear, even to Java developers. It's roughly equivalent to let, just without limiting the scope of the names.
这是您可能忽略的一个选项。
如果您确实想创建一个方法,我就会这样做。
或者,您可以创建一个避免嵌套的方法(可以将其命名为
let
):但现在您可能需要多次命名相同的内容。
Here's an option you may have overlooked.
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:But now you might need to name the same things multiple times.
如果您觉得第一种形式更干净/更优雅/更具可读性,那么为什么不坚持使用它呢?
首先,阅读 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.
为什么不在这里使用模式匹配:
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) } }
如何使用柯里化来记录函数返回值(前面参数组中的参数在后续组中可用)。
看起来有点奇怪但相当简洁并且没有重复调用:
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: