私の最初の選択は通常、再帰を使用することです。適度にコンパクトではありませんが、潜在的に高速であり(確かに遅くはありません)、早期終了でロジックをより明確にすることができます。この場合、ネストされた def が必要ですが、これは少し厄介です:
def sumEvenNumbers(nums: Iterable[Int]) = {
def sumEven(it: Iterator[Int], n: Int): Option[Int] = {
if (it.hasNext) {
val x = it.next
if ((x % 2) == 0) sumEven(it, n+x) else None
}
else Some(n)
}
sumEven(nums.iterator, 0)
}
私の 2 番目の選択肢は、 を使用するreturn
ことです。これは、他のすべてをそのまま保持し、フォールドを a でラップするだけでよいため、def
何かを返すことができるためです。この場合、既にメソッドがあるため、次のようになります。
def sumEvenNumbers(nums: Iterable[Int]): Option[Int] = {
Some(nums.foldLeft(0){ (n,x) =>
if ((n % 2) != 0) return None
n+x
})
}
この特定のケースでは、再帰よりもはるかにコンパクトです (反復可能/反復子変換を行う必要があったため、再帰では特に運が悪かったのですが)。他の条件がすべて同じであれば、制御フローの急な変化は避けるべきものですが、ここではそうではありません。価値がある場合に使用しても害はありません。
これを頻繁に行っていて、どこかのメソッドの途中で実行したい場合 (つまり、単に return を使用できなかった場合)、おそらく例外処理を使用して非ローカル制御フローを生成するでしょう。つまり、結局のところ、それが得意とすることであり、それが役立つのはエラー処理だけではありません。唯一の秘訣は、スタック トレースの生成を回避することです (これは非常に低速です)。これは簡単です。なぜなら、トレイトNoStackTrace
とその子トレイトControlThrowable
が既にそれを行っているからです。Scala はすでにこれを内部で使用しています (実際、それが折り畳みの内側からの return を実装する方法です!)。自分で作ってみましょう (入れ子にすることはできませんが、修正することはできます):
import scala.util.control.ControlThrowable
case class Returned[A](value: A) extends ControlThrowable {}
def shortcut[A](a: => A) = try { a } catch { case Returned(v) => v }
def sumEvenNumbers(nums: Iterable[Int]) = shortcut{
Option(nums.foldLeft(0){ (n,x) =>
if ((x % 2) != 0) throw Returned(None)
n+x
})
}
もちろん、ここでは使用する方が優れていますが、メソッド全体をラップするだけでなく、どこにでもreturn
配置できることに注意してください。shortcut
私にとっての次の行は、フォールドを再実装することです (私自身、またはそれを行うライブラリを見つけることのいずれか)。これを行う 2 つの自然な方法は、値を伝搬するのではなく、値Option
を含む に伝搬することです。ここで、None
は終了を意味します。または、完了を通知する 2 番目のインジケーター関数を使用します。Kim Stebel が示した Scalaz の遅延フォールドはすでに最初のケースをカバーしているので、2 番目のケースを示します (ミュータブルな実装を使用):
def foldOrFail[A,B](it: Iterable[A])(zero: B)(fail: A => Boolean)(f: (B,A) => B): Option[B] = {
val ii = it.iterator
var b = zero
while (ii.hasNext) {
val x = ii.next
if (fail(x)) return None
b = f(b,x)
}
Some(b)
}
def sumEvenNumbers(nums: Iterable[Int]) = foldOrFail(nums)(0)(_ % 2 != 0)(_ + _)
(再帰、リターン、遅延などによる終了を実装するかどうかはあなた次第です。)
私はそれが主な合理的な変種をカバーしていると思います。他にもいくつかのオプションがありますが、この場合にそれらを使用する理由がわかりません。(Iterator
があればそれ自体はうまく機能しますが、そうfindOrPrevious
ではありません。手動でそれを行うには余分な作業が必要なため、ここで使用するのはばかげたオプションになります。)