Идиоматическое решение Scala императивного кода
Каковы некоторые идеи для выражения этой функции в "идиоматическом" Scala. Или, точнее, есть ли способ удалить локальные переменные, не жертвуя удобочитаемостью?
def solve(threshold: Int)(f: Int => Int): Int = {
var sum = 0
var curr = 0
while(sum < threshold) {
sum += f(curr)
curr += 1
}
curr
}
Единственное, что я мог придумать, - это, но, на мой взгляд, он длиннее и менее читабелен.
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
}
5 ответов
Самый прямой подход - превратить цикл while во вложенную хвостовую рекурсивную функцию.
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)
}
Это стандартный "функциональный" способ зацикливания.
def solve(threshold: Int)(f: Int => Int): Int = {
Iterator.from(0).map(f).scanLeft(0)(_ + _).indexWhere(threshold <=)
}
На мой взгляд, версия цикла гораздо понятнее.
Вы могли бы
def solve(threshold: Int, i: Int = 0)(f: Int => Int) = {
if (threshold <= 0) i else solve(threshold - f(i), i+1)(f)
}
но я не уверен, что это на самом деле яснее. Обратите внимание, что на самом деле это больше символов, чем компактная версия цикла while:
def solve(threshold: Int)(f: Int => Int) = {
var s,i = 0; while (s < threshold) { s += f(i); i += 1 }; i
}
Циклы с изменяемыми переменными не всегда плохи, "идиоматичны" или нет. Просто сохраняйте изменяемое состояние в функции, и все, что кто-либо еще видит, это функция без сохранения состояния, которую можно вызвать.
Кстати, хотя sum
разумное имя переменной, curr
сомнительно Что случилось с i
? Он широко используется в качестве индексной переменной, и в любом случае наличие переменной вообще является неприятностью; Дело в том, что вы берете что-то и увеличиваете это, что бы это ни было, на один шаг каждый раз, а затем возвращаете это. Именно этот поток логики, а не имя, говорит вам (и другим), для чего он нужен.
Вот как я бы сделал это в Haskell:
solve threshold f = snd $ until test step (0, 0)
where test (sum, i) = sum >= threshold
step (sum, i) = (sum + f i, succ i)
Это четко обозначает test
, step
и начальные значения, так же, как императивная версия. Я не уверен, есть ли у Scala until
в libs где-то, но это тривиально, чтобы определить:
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
}
Мне всегда интересно, когда люди говорят о "идиоматической" скале. Потому что, на мой взгляд, у каждого свое восприятие идиоматизма. Если вы ищете функциональное решение, я хотел бы предложить вам взглянуть на "сущность шаблона итератора". На самом деле в scala есть очень хороший пост на эту тему, проверьте это здесь: http://etorreborre.blogspot.com/2011/06/essence-of-iterator-pattern.html