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

Reply via email to