(clojure/in-ns 'guesslib)
(clojure/refer 'clojure)
(def #^{:private true} thekey (gensym))
(defn new-target [mx]
(let [t (Math/floor (* mx (Math/random)))]
(fn [k] (when (= k thekey) t))))
(defn guess [tf g]
(let [t (tf thekey)]
(cond (< g t) :too-low
(> g t) :too-high
:else :you-win)))
Now in the user namespace, you could have this interaction:
user=> (refer 'guesslib)
nil
user=> (def tf (new-target 10))
#'user/tf
user=> tf
guesslib.new_target__2134$fn__2136@17cff66
user=> (tf guesslib/thekey)
java.lang.IllegalStateException: var: guesslib/thekey is not public
I've got a guess target, but I can't see it directly. However, I can
still interact with it:
user=> (guess tf 5)
:too-high
user=> (guess tf 2)
:too-low
user=> (guess tf 4)
:you-win
Nice, right? Except there's private and then there's private:
user=> (def stolen (ref nil))
#'user/stolen
user=> (clojure/in-ns 'guesslib) ; now you see where I'm going
#<Namespace: guesslib>
guess=> (dosync (ref-set user/stolen thekey))
G__2133
guess=> (in-ns 'user)
#<Namespace: user>
user=> (guess tf (tf @stolen))
:you-win
I win every time. :-/ So, my question is -- what's the right way to
do this? Or is my goal itself the problem?
Another way to go about it is to put all the functions that need
access to the target value in its scope (currently this is just
"guess", but this is just an example of a larger problem). In order
to preserve a normal calling API, I'd have to do something like:
(clojure/in-ns 'guesslib)
(clojure/refer 'clojure)
(defn new-target [mx]
(let [t (Math/floor (* mx (Math/random)))]
{'guess (fn [g]
(cond (< g t) :too-low
(> g t) :too-high
:else :you-win))}))
(defn guess [fmap & args]
(apply ('guess fmap) args))
...so the toplevel functions just delegate to the closures in the map.
I suppose one could make a macro to reduce the repetitiveness of
these top-level functions, but it still seems rather awkward.
What am I missing?
--Chouser
I realize hiding data with encapsulation is a feature of OOP. I guess
my point is that many other OOP features are currently provided by
Clojure, although often in non-traditional ways, but that I wasn't
sure about the best way to accomplish this specific feature.
> You could do it by closing over a reference:
In my previous example I did close over the data I needed, but as I
said it seems awkward to have to define the functions in one place
(the map in new-target) and then sorta proxy each one with a top-level
method.
> (let [stash (ref {})]
>
> (defn new-target [mx]
[...clip...]
This is indeed a solution to the proxying awkwardness, but I don't
think using defn inside a let is an acceptable solution in Clojure.
--Chouser
It's generally recommended in Clojure to do def or defn only at the top level.
Perhaps this case is a little different because we're not re-def'ing
an existing symbol, but I don't think it's necessary.
> I can't think of any other way you could hide a symbol.
I showed a mechanism in my first post on the subject. Instead of
doing def's in the let, you set up a map of the closures you want, and
return that. Then you can have top-level defn's that turn around and
run the closures that have access to the private locals they closed
over.
If that's the best we can do, then that's fine and I'll see if there's
some macroness that can tidy it up a bit.
--Chouser
> It's generally recommended in Clojure to do def or defn only at the
> top level.
Lambda to the rescue...
(def new-target
(let [stash (ref {})]
(fn [mx]
...)))
or as a concrete example:
(def another
(let [stash (ref 1)]
(fn [] (dosync (commute stash inc)) @stash)))
or as recently discussed:
(def another
(let [stash (ref 1)]
(fn [] (dosync (alter stash inc)))))
--Steve
>
>
> On Aug 21, 2008, at 4:08 PM, Chouser wrote:
>
>> It's generally recommended in Clojure to do def or defn only at the
>> top level.
>
> Lambda to the rescue...
>
> (def new-target
> (let [stash (ref {})]
> (fn [mx]
> ...)))
Which you already had... ' sorry for the noise.
:-)
--Steve