
Try Three Times - michaelsbradley
http://www.lispcast.com/try-three-times
======
mjt0229
That's nice, but what about adding backoff? I just came across this blog
post[1] over at AWS about exponential backoff - timely because I'd just been
working on some retry logic myself and wondered if I'd made good decisions
about how to implement jitter. Turns out, I hadn't.

[1]
[http://www.awsarchitectureblog.com/2015/03/backoff.html](http://www.awsarchitectureblog.com/2015/03/backoff.html)

------
wtbob
> You pass it a function and a number of times to retry it. The base case is
> when n is 0. In that case, it will just try it (not retry).

So, TRY-N-TIMES should either be named RETRY-N-TIMES or the base case should
be 1; as it is, `(try-n-times #(foobar) 1)` will potentially try _twice_.

------
switch007
Similar utility for Python -
[https://pypi.python.org/pypi/retry](https://pypi.python.org/pypi/retry)

------
suvelx
I'm not familar with Clojure, but isn't `(catch Throwable)` going to catch
_everything_? Shouldn't we only be retrying if an accepted failure occurs?

~~~
seabee
For that to work, you need to enumerate either the acceptable failures
(network timeouts) or the unacceptable failures (logic error).

However, for idempotent actions, why does it matter what caused the exception?
The worst that happens is you waste 3x the resources, and the benefit is you
avoid false negatives from overspecifying your unacceptable failures.

~~~
ajuc
In java there's this distinction between Error (sth that's usualy non-
recoverable), checked exception, and runtime exception. You would usually only
catch runtime exceptions here if this was java (checked are checked staticaly,
so they need to be caught anyway).

So yeah, catching throwable is wrong usually.

------
copsarebastards
Wait a second--is this really using exceptions for control flow?

One of the Pragmatic Programmer's tips[1] is "Use Exceptions for Exceptional
Problems". Network delays and problems aren't exceptional--they should be
expected. It might make sense to throw an exception if you aren't going to
handle the network issue, or if you have already tried to handle it and
failed. But if you're trying to handle the issue then it shouldn't throw an
exception.

Even if you don't buy this from a theoretical standpoint, you should look at
your performance using this method. Exception performance is terrible in
almost every language. I'm not sure how Clojure exception performance is, but
guessing from Java, it's probably not an exception (har har) to the rule.

I'm not a Clojure expert, but I would be really surprised if Clojure didn't
offer a non-exception way to do retries.

[1] [https://pragprog.com/the-pragmatic-
programmer/extracts/tips](https://pragprog.com/the-pragmatic-
programmer/extracts/tips)

~~~
Gurkenmaster
The most expensive part of Java exceptions is building the stacktrace. You can
avoid this by overriding the fillInStacktrace method with an empty
implementation.

~~~
copsarebastards
Or you could just have it not throw the exception in the first place and test
the return code, which documents your control flow much more clearly.

------
TomFrost
Similar library for (Node|io).js, with exponential backoff and randomness:
[https://github.com/TomFrost/node-attempt](https://github.com/TomFrost/node-
attempt)

------
Gurkenmaster
HTTP PUT should be used in place of HTTP POST if you need idempotency.

------
siscia
I wouldn't have use a macro...

A normal function would have done pretty much the same job...

~~~
wtbob
The advantage of a macro is that one doesn't have to wrap one's own code in a
closure. It's cleaner to write `(try-n-times (let ((foo (bar 1))) (baz foo 2))
3)` than `(try-n-times (lambda () (let ((foo (bar 1))) (baz foo 2))) 3)`.

Also: man, writing Lisp without paren-matching bites.

~~~
GregBuchholz
...but then you've just pushed the lambda somewhere else:

(map (lambda (x) (try-3 x)) collection-of-try-threeables)

...that's why a lightweight anonymous function notation is useful (Clojure's
#() reader macro, etc.).

~~~
wtbob
Ummm, `(map (lambda (x) (try-3 x)) …` could just be written as `(map #'try-3
…)`

