4.4.5 What is Best: Answer
自由回答。これは少々トリッキーな問題で、答えはどんなセマンティクスを欲するか次第になる。考えるいくつかのポイントは下記:
- エラーリカバリは大きなジョブを処理する場合に重要だ。1日分のジョブを実行し終わってから、最後の要素で失敗したことを知りたくはない。
- エラーレポートも同様に重要だ。何かがおかしい、だけでなく、具体的に何が悪くなったのかを知る必要がある。
- 多くの場合、最初に起きたエラーだけでなく、全てのエラーを一度に知りたいものだ。典型的な例はウェブフォームだ。ユーザーがフォームを入力した際に1つずつエラーを返すよりは、全ていっぺんに返したほうがはるかに良いUXといえる。
4.6.5 Exercise: Safer Folding using Eval: Answer
(オリジナルのfoldRightを再掲)
def foldRight[A, B](as: List[A], acc: B)(fn: (A, B) => B): B = as match {
case head :: tail =>
fn(head, foldRight(tail, acc)(fn))
case Nil =>
acc
}
最も簡単な方法は、foldRightEvalというヘルパーメソッドを定義することだ。これによりBをEval[B]で置き換え、呼び出し処理を Eval.deferで置き換えて再起呼び出しから守る。
@ def foldRightEval[A, B](as: List[A], acc: Eval[B]) (fn: (A, Eval[B]) => Eval[B]): Eval[B] = as match {
case head :: tail =>
Eval.defer(fn(head, foldRightEval(tail, acc)(fn)))
case Nil =>
acc
}
defined function foldRightEval
foldRightをfoldRightEvalを使って再定義すれば、スタックセーフになる。
@ def foldRight[A, B](as: List[A], acc: B)(fn: (A, B) => B): B = {
foldRightEval(as, Eval.now(acc)) {
(a, b) => b.map(fn(a, _))
}.value
}
defined function foldRight
@ foldRight((1 to 100000).toList, 0L)(_ + _)
res58: Long = 5000050000L
入れ子にする形でも書ける。
@ def foldRight2[A, B](as: List[A], acc: B)(fn: (A, B) => B): B = {
def loop[A, B](as: List[A], acc: Eval[B])(fn: (A, Eval[B]) => Eval[B]): Eval[B] =
as match {
case head :: tail =>
Eval.defer(fn(head, foldRightEval(tail, acc)(fn)))
case Nil =>
acc
}
loop(as, Eval.now(acc)) { (a, b) => b.map(fn(a, _)) }.value
}
defined function foldRight2
@ foldRight2((1 to 100000).toList, 0L)(_ + _)
res60: Long = 5000050000L