errfrom
errfrom

Reputation: 253

Is there any method of making local 'defonce'? (Clojure)

Let's say I need to make a simple counter and I want counter to be incremented each time I call this function, but here is one unpleasant thing: defined 'counter' is not local and I can easily change its value from another space, that breaks encapsulation.

(defn next []
  (defonce counter (atom 0))
  (println @counter)
  (reset! counter (inc @counter)))

Many say, it will be correct if I place 'private' meta tag. So function will look like this:

(defn next []
  (defonce ^:private counter (atom 0))
  (println @counter)
  (reset! counter (inc @counter)))

But I still have access to 'counter' from another space.
Is there any way to implement this encapsulation or it's only at the agreement level?

Upvotes: 1

Views: 397

Answers (2)

Sam Estep
Sam Estep

Reputation: 13334

Here's how you should write your next function:

(def ^{:arglists '([])} next
  (let [counter (atom 0)]
    #(let [after (swap! counter inc)
           before (dec after)]
       (println before)
       after)))

This is the same as the one in your question, except that it is thread-safe and completely encapsulates the counter atom.

Upvotes: 4

mishadoff
mishadoff

Reputation: 10789

Private works well, you shouldn't have access from other namespace

user> (ns a)
nil
a> (defonce ^:private counter (atom 0))
#'a/counter
a> (defn next [] 
     (println @counter)
     (swap! counter inc))
#'a/next
a> (next)
0
1
a> (next)
1
2
a> (next)
2
3
a> (next)
3
4
a> (ns b)
nil
b> (require 'a)
nil
b> (a/next)
4
5
b> (a/next)
5
6
b> a/counter
CompilerException java.lang.IllegalStateException: var: a/counter is not public
b> (ns a)
nil
a> a/counter
#object[clojure.lang.Atom 0x1ec64eb6 {:status :ready, :val 6}]

Also some minor issues:

  1. define counter at the top level of ns, not inside the function, both have the same effect, but top level is clearer
  2. change reset! to (swap! counter inc), it will be thread safe

Upvotes: 1

Related Questions