Clojure的“let”在Scala中等效 [英] Clojure's 'let' equivalent in Scala

查看:173
本文介绍了Clojure的“let”在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 - 与 comprehension我可以实现类似的东西:

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

一方面,这个解决方案具有平面结构,就像在Clojure中的 let ,但从另一方面来说,在选项中接收结果,并从计算结果中接收选项 >



有没有更好的方法实现我的目标?什么是处理这种情况的惯用方法(可能我应该留在 val s ...但 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 firstFn){first => let(secondFn(first)){second => thirdFn(first,second,second + a)}}

当然,它仍然是嵌套的。不能避免。但你说你喜欢monadic形式。所以这里的身份monad

 案例类Identity [A](x:A){
def map [B] (f:A => B)= Identity(f(x))
def flatMap [B](f:A => Identity [B])= f(x)
} $ b

这里是你的monadic计算。通过调用.x打开结果

  def calculateMonad(a:Long)= for {
first < (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


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.

解决方案

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

这篇关于Clojure的“let”在Scala中等效的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆