Skip to content

Instantly share code, notes, and snippets.

@HerringtonDarkholme
Created December 10, 2014 15:03
Show Gist options
  • Save HerringtonDarkholme/4bcdff2f3a978597372f to your computer and use it in GitHub Desktop.
Save HerringtonDarkholme/4bcdff2f3a978597372f to your computer and use it in GitHub Desktop.
foldLeft and foldRight
def retry(noTimes: Int)(block: ⇒Future[T]): Future[T] = {
val ns: Iterator[Int] = (1 to noTimes).iterator
val attempts: Iterator[Future[T]] = ns.map(_⇒ ()⇒block)
val failed = Future.failed(new Exception)
attempts.foldLeft(failed)
((a,block) ⇒ a recoverWith { block() })
}
retry(3) { block }
= unfolds to
((failed recoverWith block1) recoverWith block2) recoverWith block3
def retry(noTimes: Int)(block: ⇒Future[T]): Future[T] = {
val ns: Iterator[Int] = (1 to noTimes).iterator
val attempts: Iterator[Future[T]] = ns.map(_⇒ ()⇒block)
val failed = Future.failed(new Exception)
attempts.foldRight(() ⇒ failed)
((block, a) ⇒ () ⇒ { block() fallbackTo { a() } })
}
retry(3) { block } ()
= unfolds to
block1 fallbackTo { block2 fallbackTo { block3 fallbackTo { failed }}}
@HerringtonDarkholme
Copy link
Author

Once again, the association of operation is the key factor when choosing one over another between foldLeft and foldRight.

Composing Future[T] requires creating new objects, lazily by the nature of call-by-name parameter. foldLeft packs newly created object into the previously created object's recoverWith. So it needs to evaluate every Future 's recoverWIth when it fulfills future. Contrarily, foldRight holds to fallback hastily and, instead, fallback only when needed.

The laziness of call-by-name parameter is the key here. (Consider, if fallback is evaluated eagerly then no difference shall exists between them). Laziness avoids unnecessary calls. Becuase the recoverWith's lazy parameter is the right one, it is better modeled as right-associative. (d op (c op (a op b)))

It is much like list pushing and concatenation. Concatenating two lists are better modeled as a right-associative operation like (f2 (f1 (e1 (e2 e3)))) because lpush is O(1), while left-associative concatenation (f1 (f2 f3)) ::: (e1 (e2 e3)) is much more expensive.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment