Clojure's 'let' equivalent in Scala

社会主义新天地 提交于 2019-11-30 06:38:51

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

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.

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.

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.

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:

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

println(calculate(1L)()())
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!