Right, nesting everything inside a single function makes it impossible to unit test the inner functions--they don't even have names in the global scope! In retrospect, I think the solution I posted is more cute than it is practical. Passing around a map of intermediate values seems to be the winner all around, but it still seems a little odd to me. Idiomatic Clojure code manipulates maps far more than I've seen in any other language.
On Fri, Feb 19, 2010 at 11:30 AM, Yaron <ygol...@gmail.com> wrote: > With this approach how would I test the individual functions defined > inside of the let? Wouldn't they be invisible to me and the test > framework which would only see "sell-or-rent"? > > On Feb 18, 4:27 pm, John Williams <j...@pobox.com> wrote: > > I'm no Clojure guru myself, but one approach you may want to consider is > > nest all your auxiliary functions inside the main function, and use > ordinary > > let-bindings the same way you've been trying to use global bindings: > > > > (defn sell-or-rent [{:keys [supplied-1 supplied-2]}] > > (let [derived-1 (+ 1 supplied-1) > > derived-2 (* 2 supplied-2)] > > (letfn [(f [x y z] ...) > > (g [x] ...)] > > ...))) > > > > This has the disadvantage of indenting all your helper functions much > more > > than they would be otherwise, but it gets the job done without any messy > > global variables or thunks. If you want to expose more functions than > just > > sell-or-rent, you could write some very OO-flavored code: > > > > ;; Define a function to create objects. > > (defn pseudo-constructor [{:keys [supplied-1 supplied-2]}] > > (let [member-var-1 (+ 1 supplied-1) > > member-var-2 (* 2 supplied-2)] > > (letfn [(private-method-1 [] "priv1") > > (private-method-2 [] "priv2")] > > {:public-method-1 (fn [x y z] "pub1") > > :public-method-2 (fn [x y] "pub2")}))) > > > > ;; Use the object. > > (let [obj (pseudo-constructor {:supplied-1 1 > > :supplied-2 2})] > > (println ((:public-method-1 obj) "x" "y" "z")) > > (println ((:public-method-2 obj) "x" "y"))) > > > > --jw > > > > On Mon, Feb 15, 2010 at 11:24 AM, Yaron <ygol...@gmail.com> wrote: > > > I am writing a calculator to figure out if I should sell or rent my > > > home using Clojure. This is my first Clojure program so I'm about as > > > wet behind the ears as it gets. So far everything is actually going > > > really well (reminds me of the fun I had with Scheme in college) but > > > for one thing. My calculator needs 30+ arguments from the user in > > > order to run. Furthermore I have a bunch of secondary values that are > > > derived from the arguments the user submits. > > > > > So imagine the user submits a value A. I will have a value B whose > > > definition will be something like (+ 1 A) (yes, more complex in > > > reality, but you get the idea). > > > > > If I were back in Java or C# I would define a class, submit A (and > > > it's 29+ friends) in the constructor and then create a property on the > > > class B. > > > > > In Clojure I have taken a different approach. I first create (def *A* > > > 3) where 3 is a completely bogus value I just made up. Then at run > > > time I use bindings to re-bind A to the actual value the user passed > > > in. > > > > > But my problem is, what to do about B? I thought of doing something > > > like (def *B* 3) and then in the binding passing in a function like > > > (defn B-Gen [] (+ *A* 1)) to create a new binding to B but I quickly > > > realized this would be a bug inducing nightmare. If I forget to > > > include one of the derived values in the binding or put them in the > > > wrong order then I would get the wrong value. > > > > > So what I currently do is: > > > (def *A* 3) ; A bogus value that will later be rebound > > > (defn B [] (+ *A* 3)) > > > > > The good news is, that this works and doesn't require any book > > > keeping. > > > > > The bad news is that it's ugly. If I want to do something trivial like > > > divide B by 2 I have to call B as a function(/ (B) 2) instead of the > > > more natural (/ B 2). And of course this approach is pretty > > > unfortunate from a performance perspective as I'm constantly having to > > > recalculate what are effectively static values. Yes, I could use > > > memoization but many of these values are pretty trivial (usually just > > > algebra equations) and I suspect the overhead of memoization exceeds > > > the perf improvement. > > > > > But in any case the whole approach of having to take what really are > > > static values and turn them into functions feels really hacky. So my > > > guess is that I'm thinking about this problem the wrong way. I'm stuck > > > in my old imperative/OO constructor world. > > > > > What's the right way to think about primary values that will be > > > rebound (once) that then have dependent values that need to be > > > recalculated when that rebinding happens? > > > > > Thanks, > > > > > Yaron > > > > > -- > > > 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<clojure%2bunsubscr...@googlegroups.com> > <clojure%2bunsubscr...@googlegroups.com<clojure%252bunsubscr...@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 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<clojure%2bunsubscr...@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 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