Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to dereference Future in Clojure?

Tags:

clojure

My program works as following: two futures transfer a specific amount from one balance A to balance B after some random wait-time. Each repeats doing that 10 times. So I try to stop the program and deref the future, but it doesnot work, it throws NullPointerException as I deref it. What have I done wrong?

And I want a final result that displays balanceA and balanceB at the last line before the program ends. Right now it does outputs the result but it appears inside the "transfer" and "try". What do I have to do?

Thank you.

  (def balanceA (ref 1000))
  (def balanceB (ref 2000))
  (def agentCount (agent 1))

   ;;transfer function
  (defn transfer [balanceA balanceB amount futureNum waitingTime]
    (dosync(alter balanceA - amount))
    (Thread/sleep waitingTime)
    (dosync(alter balanceB + amount))
    (do(
        (println "Trying" futureNum waitingTime)
        (println "Transfered" futureNum @agentCount)
        (send-off agentCount inc))))

  ;;futureA 
  (dotimes [n 10](def futureA (future (transfer balanceA balanceB 20 1 (rand-int 100)))))

  ;;futureB
  (dotimes [n 10](def futureB (future (transfer balanceA balanceB 15 2 (rand-int 40))(prn "result" @balanceB @balanceB))))

  ;;print out the result
  (println "result" @balanceA @balanceB)

  ;;dereference futures
  (@futureA)
  (@futureB

Here is my output:

  user=> (load-file "assignment10.clj")
  Result: Trying: Trying: 2650  2000
  2 Trying: 2 Trying: 2 3
   Transfered: 2 1

  user=> Trying: 1 28
  Transfered: 1 2
  Trying: 1 28
   Transfered: 1 3
  Trying:Trying:  21  2218

  Transfered: 2 4
  Transfered: 1 4
 97

  Transfered: 2 6
  10
   Trying: 2 26
  Transfered: 2 7
  Transfered: 2 Trying: 8
  2Trying:Transfered:   227
  2 28
  Transfered:7
  Transfered: 2 9
  2 9
  Trying: 2 33
  Transfered: 2 12
  Trying: 1 49
   Trying: 2 39
  Transfered:Transfered:  21  1313

  Trying: 1 57      
  Transfered: 1 15
  Trying: 1 71
  Transfered: 1 16
  Trying: 1 76
  Transfered: 1 17
  Trying: 1 81
  Transfered: 1 18
  Trying: 1 93
  Transfered: 1 19
  Trying: 1 98
  Transfered: 1 20
like image 582
user1874435 Avatar asked Jul 19 '26 01:07

user1874435


2 Answers

There are a couple places where you have some extra ()s, either of which could be causing the NullPointerExceptions

(@futureA)

means first get the current value from the ref, and then take that value and call it as a function.

(do(
    (println "Trying" futureNum waitingTime)
    (println "Transfered" futureNum @agentCount)
    (send-off agentCount inc))))

the extra set of ()s after the do cause it to call the result of printing "Trying" as a function, who's first argument is the resulf of printing "Transfered" and second argument is the result of calling send-off. Since the println always returns nil this will result in a NPE. It seems likely that these ()s are unintentional.

It's unusual to call def in anything but a top level form (unless you are writing macros). In this case only the last occurance of futureA and futureB will be useful. It might be worth considering using for instead and then saving the result of all the futures in a sequence so you can see if any of them fail, rather than only being able to tell if the last one is failing.

like image 119
Arthur Ulfeldt Avatar answered Jul 22 '26 08:07

Arthur Ulfeldt


There are more things that could be considered problematic with that piece of code. I'll clean it up a bit to give you some reference on how to approach problems like this. Hope you don't mind.

Make yourself clear what you want to do:

  1. Print random waiting time.
  2. Reduce balanceA by some amount.
  3. Wait for said time.
  4. Increase balanceB by the same amount.
  5. Print the amount of data transferred.

Clearly, you have two "moving parts" - the balances - and these are independent from each other. If you read things like this blog post you'll see that the right choice for state management here would be an atom. Let's model the transaction delay using the above scheme:

(def balance-a (atom 1000))
(def balance-b (atom 2000))

(defn transfer!
  [id amount delay-ms]
  (println id ": transferring with delay of" delay-ms "milliseconds ...") ;; 1.
  (swap! balance-a - amount)                                              ;; 2.
  (Thread/sleep delay-ms)                                                 ;; 3.
  (swap! balance-b + amount)                                              ;; 4.
  (println id ": transfer done."))                                        ;; 5.

You want threads that call this function with different values. (I'd prefer spawning Threads to not be limited by the size of the future thread pool but futures also work in this case.) You don't need to dereference a future for it to run - only if you want to wait for it to finish.

This means that if you want to wait for all the futures you create, you have to store them somewhere. (In your case, using def is not only unidiomatic but will also only retain the last created future.)

(defn start-transfer!
  [n id amount max-delay-ms]
  (doall
    (for [_ (range n)]
      (future (transfer! id amount (rand-int max-delay-ms))))))

This creates a list (actually a seq) of futures that perform the transfer. You can store this list and then dereference the single elements to wait for all transactions to finish:

(def futures-a (start-transfer! 10 :first  20 100))
(def futures-b (start-transfer! 10 :second 15 40))

(doseq [f futures-a] @f)
(doseq [f futures-b] @f)

Now, at this point, nothing is running anymore. You can then read the balances:

(println "balances:" @balance-a "vs." @balance-b)

Note that you will get quite the messy output on your console since the different println statements will interfere with each other heavily. This is, e.g. why you had lines like Transfered:Transfered: 21 1313 in your original paste.

There is a lot in here but I think that if you take the time to think about the different things mentioned in this post you'll at least have some useful insights.

like image 38
xsc Avatar answered Jul 22 '26 10:07

xsc



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!