I ended up accidentally injecting a completely different thread of
discussion into the "Is Clojure right for me?" thread.  I'm breaking it
into a separate thread here.

Here's where we left off:

On Fri, Dec 27, 2013 at 6:34 AM, Stuart Halloway
<stuart.hallo...@gmail.com>wrote:

> Yes, thanks Mark. It seems to me that you are saying "namespaces make poor
> maps".  Stipulated.  So why not use records or maps?
>
>
This is close, but not exactly what I'm saying.  It's not so much about
wanting namespaces to be maps.  It's about wanting a robust mechanism for
functions to share information other than threading that information
through the functions.

In Structure and Interpretation of Computer Programming, a large chunk of
the programs are written in a pseudo-object-oriented style, a style which
simulates objects by having functions' closures share the same local
context.

Sure, you could do that with maps:

For example:

(defn create-functions [init-info]
   (let [shared-info init-info]
      {:foo (fn foo [x] ... body uses shared-info in some way ...),
       :bar (fn bar [x] ... body uses shared-info in some way ..)}))

Then, to access these functions, you'd do something like this:
(def instance (create-functions default-info))
((:foo instance) 2)  ; the equivalent of instance.foo(2) in OO
((:bar instance) 3)  ; the equivalent of instance.bar(3) in OO

Of course, SICP's main point is that even bare-bones Scheme is rich enough
to simulate objects, but the other point is that it is important for
functions to be able to share stat*e* which can be initialized for a group
of functions,

*even if that state is immutable.*
The above pattern is important enough that most languages provide some kind
of mechanism for doing that. Classes are one such pattern.

In Clojure, the above code would be horribly un-idiomatic of course.
Actually, depending on the functions and definitions, it might not even be
possible to structure the code that way (because Clojure's local definition
capabilities are not as rich as what is possible at the global level, for
example, regarding mutual references -- in some contexts, letfn might help,
but not all contexts).

The above example uses associative containers for the shared-info, and
associative containers to hold the group of related functions that are
outputted, but that doesn't make this solution any more attractive --
again, emphasizing that this is not about associative containers.

I claim that Clojure only provides two idiomatic solutions to the above
problem of functions sharing the same immutable information, which might
need to be set prior to using those functions:

Solution 1:

(def ^:dynamic shared-info default-info)
(defn foo [x] ... body uses shared-info)
(defn bar [x] ... body uses shared-info)

Call these functions via:

(foo 2) and (bar 3) if you're happy with the defaults or

(binding [shared-info info] (foo 2)) and
(binding [shared-info info] (bar 3)) otherwise.

This is awkward for several reasons, and sometimes this solution isn't
really an option since functions that return lazy structures won't work
with the above mechanism.

Solution 2:

(defn foo [shared-info x] ... body uses shared-info)
(defn bar [shared-info x] ... body uses shared-info)

Call these functions via:

(foo info 2)
(bar info 3)


My argument is that both these solutions are unsatisfying.  Solution 2 is
irritating because if you use a map for shared-info, you end up having to
repeat the destructuring in every single function.  Also, you need to
thread this information through all the functions, not only the functions
that directly use, but also the functions that call other functions that
use it.  It makes all the functions bulky and awkward, and at the end of
that, you still don't have something that reflects the notion of getting
back a group of functions that share the *same* info -- instead, you have
to remember to always pass in that same info with every function call.
Also, with the threading-the-state version, you don't have a convenient
notion of "default state", unless you are willing to take cases on the
number of arguments for every single one of your functions.

My other claim is that "Solution 2" feels too heavy-handed for small
projects, or scenarios where it isn't clear you'll ever want to initialize
the shared-info to something other than the defaults, but the path of
transition from Solution 1 to Solution 2 is a painful one.


I think that where my experience differs from yours is summarized in your
> comment "In Clojure, a project often begins with a bunch of functions
> sharing some immutable state or "magic constants" stored in vars." I
> *never* do that.  If there are a group of related things, I put them in an
> associative container (maybe just a map) on day one.
>

Even if you share all those constants in an immutable container, I maintain
that the threading of that information through all the functions can be
awkward.


>
> The analogy with OO is misleading here.  If you started an OO project
> putting everything in static/singleton members, you would have the same
> pain you attribute to Clojure's namespaces.
>

Not necessarily.  Sure, you'd have to delete all the "static" words from
your methods.  And you'd need to make sure you then accessed the methods
through an instance, but fundamentally, you wouldn't need to change the
body of any of the methods or the number of inputs in callers or callees.
I suspect in a language like Scala, you could probably even find a way to
leave behind a "companion object" that forwards to a default instance, so
your old static/singleton calls still work.

So yes, I agree that there would be some similar issues in moving from a
singleton to an instance-based scheme in an OO language, but I don't think
the change would be as dramatic as the same transition in Clojure.


>
> I find this to be a "my car doesn't have wings" argument, and the kind of
> thing that leads into casually complecting everything with everything else.
>
>
If there is an idiomatic way in Clojure to solve this problem other than
the two solutions I've outlined above, I'd love to see it.

-- 
-- 
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clojure@googlegroups.com
Note that posts from new members are moderated - please be patient with your 
first post.
To unsubscribe from this group, send email to
clojure+unsubscr...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
--- 
You received this message because you are subscribed to the Google Groups 
"Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to clojure+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.

Reply via email to