clj-commons / manifold

A compatibility layer for event-driven abstractions
1.02k stars 106 forks source link

Question: better explanation of d/finally clause #168

Closed igrishaev closed 5 years ago

igrishaev commented 5 years ago

Hi, I'm a bit confused with the d/finally statement. I thought it plays the same role as the standard finally does, e.g. to perform an action even if an exception was thrown.

Here is an example of my code. My goal is to calculate a value, process its result, catch and log an exception that might happen. In addition, I'd like to recur to the head of the loop cycle. I believe the best way to do that is to add a d/finally which has a single d/recur statement but is seems to not work.

Maybe I just misunderstand the meaning of d/finally? What would be a proper pattern in my case?

(d/loop []

  (->

   (d/future
     (some-action))

   (d/chain

    (fn [result]
      (some-processing result)))

   (d/catch
       Exception
       (fn [^Exception e]
         (log/errorf e "Error while processing")))

   (d/finally
     (fn []
       (d/recur)))))
igrishaev commented 5 years ago

OK, people in Slack suggest doing it in this way:

(d/loop []

  (->

   (d/future
     42)

   (d/chain

    (fn [result]
      (/ result 0)))

   (d/catch
       Exception
       (fn [^Exception e]
         (log/errorf e "Error while processing")))

   (d/chain
    (fn [& _]
      (d/recur)))))
igrishaev commented 5 years ago

Might be closed now, but would like to hear feedback on that first.

alexander-yakushev commented 5 years ago

(d/recur) doesn't do anything by itself. It actually just returns a special marker object. You can see this by doing:

user=> (d/recur)
#object[manifold.deferred.Recur 0x6fed0534 {:status :ready, :val nil}]

It is d/loop which spins the loop as long as it sees this marker object as the result of each iteration.

Now, d/finally doesn't change the result of the deferred it wraps – it can only perform side effects.

user=> (-> (d/success-deferred 3) (d/finally (fn [] 4)))
<< 3 >>

So, in your example, (d/finally (fn [] (d/recur)) is effectively a no-op.