1

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?

errfrom
  • 243
  • 2
  • 8

2 Answers2

4

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.

Sam Estep
  • 12,974
  • 2
  • 37
  • 75
1

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
mishadoff
  • 10,719
  • 2
  • 33
  • 55
  • There was a small misunderstanding: I want counter to be only in this function with no access from the top level as a local binding 'let' do. Is it possible? – errfrom Jul 07 '17 at 11:36
  • @errfrom well, I don't think you can hide local mutable inside a function with no access from outside. Use defrecord or deftype instead (similar to creating a Counter object in java) – mishadoff Jul 07 '17 at 12:32
  • I revised my code and decided I should put the part of it in a separate module, where defining at the top level wouldn't create any problems, so basically the problem is solved. Thank you =) – errfrom Jul 07 '17 at 13:34