简体   繁体   中英

Idiomatic Scala solution to imperative code

What are some ideas for expressing this function in 'idiomatic' Scala. Or more precisely, is there a way to remove the local vars without sacrificing readability?

def solve(threshold: Int)(f: Int => Int): Int = {
  var sum = 0
  var curr = 0
  while(sum < threshold) {
   sum += f(curr)
   curr += 1
  }
  curr
}

The only thing I could come up with was this, but it's longer and less readable in my opinion.

def solve2(threshold: Int)(f: Int => Int): Int = {
  val resultIterator = Iterator.iterate (0, 0) { case (curr, sum) =>
    (curr + 1, sum + f(curr))
  }
  (resultIterator find (_._2 >= threshold)).get._1
}
def solve(threshold: Int)(f: Int => Int): Int = {
  Iterator.from(0).map(f).scanLeft(0)(_ + _).indexWhere(threshold <=)
}

In my opinion, the loop version is much clearer.

The most direct approach is to turn the while loop into a nested tail-recursive function.

def solve(threshold: Int)(f: Int => Int): Int = {
    def solveLoop(sum: Int, curr: Int): Int = if (sum < threshold) {
        solveLoop(sum + f(curr), curr + 1)
    } else {
        curr
    }
    solveLoop(0,0)
}

This is the standard "functional" way of looping.

You could

def solve(threshold: Int, i: Int = 0)(f: Int => Int) = {
  if (threshold <= 0) i else solve(threshold - f(i), i+1)(f)
}

but I'm not sure that's actually clearer. Note that it's actually more characters than a compact version of the while loop:

def solve(threshold: Int)(f: Int => Int) = {
  var s,i = 0; while (s < threshold) { s += f(i); i += 1 }; i
}

Loops with mutable variables aren't always bad, "idiomatic" or no. Just keep the mutable state safely contained within the function, and all anyone else sees is a stateless function to call.

Incidentally, although sum is a sensible variable name, curr is questionable. What's wrong with i ? It's widely used as an index variable, and anyway, having a variable at all is a nuisance; the point is you take something and increment it, whatever it is, by one step each time, and then return it. It is this flow of logic, not the name, which tells you (and others) what it is for.

Here is how I would do it in Haskell:

solve threshold f = snd $ until test step (0, 0)
  where test (sum, i) = sum >= threshold
        step (sum, i) = (sum + f i, succ i)

This clearly marks the test , the step , and the initial values, just like the imperative version. I am not sure if scala has until in the libs somewhere, but it is trivial to define:

def until[A](test: A => Boolean)(f: A => A)(v: A): A = {
  if (test(v)) {
    v
  } else {
    until(test)(f)(f(v))
  }
}

def solve(threshold: Int)(f: Int => Int): Int = {
  def test = (sum: Int, i: Int) => sum >= threshold
  def step = (sum: Int, i: Int) => (sum + f(i), i + 1)
  until(test.tupled)(step.tupled)((0, 0))._2
}

I always wonder when people talk about 'idiomatic' scala. Because in my opinion everyone has his own perception of idiomatic. If you are looking for a functional solution I would like to suggest you to take a look at the 'essence of the iterator pattern' . There is actually a very good blogpost in scala about this check it out here : http://etorreborre.blogspot.com/2011/06/essence-of-iterator-pattern.html

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM