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:
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.
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)()())
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.