So I rewrote the whole kit and kaboodle. My understanding of your mail
led me to take the approach of defining functions who take as
arguments the invariant values and who output functions that take
variant values. For example:
I'm not sure how much functional programming experience you have, but
you've essentially discovered a kind of currying.
http://en.wikipedia.org/wiki/Currying
That is, you're taking a function house-sale-profit, in terms of house-
sales-price (a function), house-sale-expenses (a function), and m, and
fixing the first two values, returning a function in terms of m.
This is seamless in Haskell; less so in Clojure. See the built-in
function `partial`.
When you wrote
(defn house-sale-profit
[house-sales-price house-sale-expenses]
(fn [m]
(- (house-sales-price m) (house-sale-expenses m))))
and used it like this:
(let [...
house-sale-profit-fn (house-sale-profit house-sales-price
house-sale-expenses)
...]
...)
you could just as easily have written:
(defn house-sale-profit
[house-sales-price house-sale-expenses m]
(- (house-sales-price m) (house-sale-expenses m)))
and used it like this:
(let [...
house-sale-profit-fn (partial house-sale-profit house-sales-
price house-sale-expenses)
...]
...)
This makes house-sale-profit a perfectly normal function, but still
allows you to partially evaluate it to yield a closure.
In this example both house-sales-price and house-sale-expenses are
actually themselves functions which I would have had to have called
previously to get their instance functions and passed in as arguments.
By doing this you're pretty much writing your own interpreter, as you
figured out :)
So is this the way you would approach this problem in Clojure?
No. (However, consider that I'm as likely to be wrong as anybody else,
and also that you're learning a ton by trying different approaches!)
I would do things much more simply: rather than (to take your example)
defining house-sale-profit in terms of two functions and a month
value, which is threaded into those functions (providing opportunity
for breakage should the signature of, say, house-sales-price change),
I would simply *define house-sale-profit in terms of the sales price
and expenses*. Rely on the price and the expenses having been
calculated outside the function.
No nonsense with throw-if-in-seq and all the other complicated
machinery you have built.
(defn house-sale-profit
[house-sales-price house-sale-expenses]
(- house-sales-price house-sale-expenses))
This is the literal definition of profit; you can write tests for this
with just two numbers. If your expenses don't care about a number of
months, or they care about something else, then just pass in a
different value -- no futzing with functions. It's more efficient, too
-- no anonymous functions.
For a single invocation you're only using one value of m, with one
house sales price, and one set of expenses. Just put them in the let!
(let [m <from the user>
price ...
expenses ...
profit (house-sale-profit price expenses)
...]
...)
I've done this for some of your functions:
<http://twinql.com/tmp/rent.clj>
I haven't tried actually using it to compute anything, but it should
give you an idea of the style I'd use.
I would guess that the whole program, neatly laid out and commented,
should come to only a couple hundred lines. Each individual function
is trivially testable and reusable, as is every composition of
functions that goes into computing the final answer.
If you want to be neater, split `sell-calculator` into two or more
functions; perhaps one which walks through the rent calculation, and
another which walks through the house sale calculation. That way each
individual function remains short and sweet.
Note that I still curry some functions (e.g., inflate), and if I
bothered to implement the agent fees (which are inflation-linked) I
might do it by passing in that curried `inflate` function. (Then
again, I might not, if I can phrase them in terms of constant values
which are computed elsewhere.) Most of the intermediate values are
simply computed directly.
Note also that, if you wished, you could completely eliminate the
`let` form, turning this whole calculation into a single (slightly
redundant) tree. I don't advocate that as particularly good style, but
it's possible.
Ultimately, all you're doing here is writing a set of functions and
combining them together to produce an answer: a simple matter of
traditional programming. It's not rocket surgery (as they say
nowadays), and so any solution which requires you to write code to
detect recursive calls, look up function names at runtime, manage a
huge intermediate map of results, etc. is probably a sign that you're
over-engineering things.
I would have used almost the same technique in, say, Pascal: define a
bunch of functions, and use a bunch of local variables in the calling
function to store intermediate values; gradually work towards the
final answer. Clojure can make this simpler (using macros, higher-
order functions, partial evaluation, and so on), but it doesn't really
change the nature of the work.
(Of course, traditional imperative languages would have given you a
mutable global chalkboard on which to play concurrency Hangman, but
for a one-time calculator that's no real problem.)
Sure, if you had a hundred thousand intermediate results, and a
thousand inputs, you wouldn't do it this way... but if that were true,
I'd be advising you to use a tool which is more suited, such as a
spreadsheet or some other dataflow system.
HTH.
-R
--
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