Richard, I spent quite a bit of time thinking about what you said.

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:

(defn house-sale-profit
 [house-sales-price house-sale-expenses]
 (fn [m]
  (- (house-sales-price m) (house-sale-expenses m))))

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.

To test out this idea I decided to implement all the functions needed
to implement the sell function from my paper. But when I was done and
wanted to actually calculate a sell value I had to write something
like:

(defn sell-calculator
 [{:keys [real-monthly-opportunity-cost months-to-find-tenant months-
in-lease lease-cycles months-to-sell excise-tax
    months-in-loan original-loan-amount monthly-loan-interest other-
sales-fees-0 monthly-inflation-rate
    selling-agent-fee-type selling-agent-fee-number buying-agent-fee-
type buying-agent-fee-number
    house-sales-price-0 loan-month-at-start]}]
 (let [inflate (inflate monthly-inflation-rate)
       house-sales-price (house-sales-price house-sales-price-0
inflate)
       buying-agent-fee (agent-fee inflate buying-agent-fee-type
buying-agent-fee-number house-sales-price)
       selling-agent-fee (agent-fee inflate selling-agent-fee-type
selling-agent-fee-number house-sales-price)
       other-sales-fees (other-sales-fees other-sales-fees-0
inflate)
       remaining-loan-balance (remaining-loan-balance months-in-loan
original-loan-amount monthly-loan-interest loan-month-at-start)
       house-sale-expenses (house-sale-expenses house-sales-price
excise-tax buying-agent-fee selling-agent-fee other-sales-fees
remaining-loan-balance)
       house-sale-profit (house-sale-profit house-sales-price house-
sale-expenses)
       months-in-business (months-in-business months-to-find-tenant
months-in-lease lease-cycles months-to-sell)]
        (sell house-sale-profit real-monthly-opportunity-cost months-in-
business)))

Writing the previous wasn't much fun and I recognized that I would
have to build the equivalent for testing all the various sub-functions
and I can only imagine what trying to do any kind of refactoring or
adding new functions would be like.

But then it occurred to me that all I was really doing was building up
a parse tree and that is something that computers are really good at.
So why not just have the computer do the work for me? So I wrote a
function called fill-map. It takes a keyword that maps to the name of
a function and a map that contains all the user arguments. It turn
returns a map that contains the submitted function and all of its
dependent functions. (Having the whole map including the dependent
functions is extremely useful for testing)

(defn fill-map
 ([func-name-keyword filled-map] (fill-map func-name-keyword filled-
map []))
 ([func-name-keyword filled-map ancestorvector]
  {:pre [(keyword? func-name-keyword) (map? filled-map) (vector?
ancestorvector)]}
  (if (contains? filled-map func-name-keyword)
   filled-map
   (do
    (throw-if-in-seq func-name-keyword ancestorvector)
    (let [func-name-symbol (keyword-to-symbol func-name-keyword)
          func (func-keyword-to-func-pointer func-name-keyword)
          func-arguments (func-keyword-to-func-keyword-arglist func-
name-keyword)
          ancestorvector (conj ancestorvector func-name-keyword)
          filled-map (reduce #(fill-map %2 %1 ancestorvector) filled-
map func-arguments)]
         (assoc filled-map func-name-keyword (apply func (map #(filled-
map %1) func-arguments))))))))

I also wrote:
(defn stack-em
 [func-name-keyword filled-map]
 ((fill-map func-name-keyword filled-map) func-name-keyword))

So now I can just say:

(defn sell-calculator
 [userargs]
 (stack-em :sell userargs))

The previous creates the same parse map as I manually created above
but in a fully automated fashion. And if I change any of the
signatures, dependencies, stick in new things, etc. it's no problem,
everything gets automatically regenerated.

You can see the whole program at 
http://www.goland.org/simple-reverse-rent-or-sell.clj

So is this the way you would approach this problem in Clojure?

  Thanks,

       Yaron

On Feb 19, 10:15 pm, Richard Newman <[email protected]> wrote:
> > I have to think that's preferable to submitting 30+ arguments to rent
> > and sell.
>
> > Or were you suggesting a different approach?
>
> The different approach only works with a different approach :)
>
> The way you've structured run-calculator, using the map is best,  
> because 30 arguments is crazy.
>
> Your need to pass 30 arguments to rent and sell because they need to  
> obtain or compute all of their intermediate values. You have a tree,  
> with rent and sell doing all the work.
>
> The alternative approach is to make your functions take the  
> intermediate values as arguments explicitly, and define those  
> functions much like your equations. You then bind the intermediate  
> values in a let... perhaps best shown by example. Instead of
>
> (defn house-depreciation
>         "The amount of depreciation that can be claimed on Federal Income  
> Taxes in year y"
>         [y args]
>         {:pre [(valid-year? y args)]}
>         (cond
>                 (> y 27) 0
>                 (< y 27) (/ (house-sales-price 0 args) 27.5)
>                 (= y 27) (* 0.5 (/ (house-sales-price 0 args) 27.5))))
>
> you would write
>
> (defn house-depreciation
>         "The amount of depreciation that can be claimed on Federal Income  
> Taxes in year y"
>         [y house-sale-price]
>         {:pre [(valid-year? y args)]}
>         (cond
>                 (> y 27) 0
>                 (< y 27) (/ house-sale-price 27.5)
>                 (= y 27) (* 0.5 (/ house-sale-price 27.5))))
>
> This transformation applies to all of your functions, so even rent and  
> sell get defined in terms of their intermediate values, just as I  
> showed in a much earlier email.
>
> Your calculator would become
>
> (defn run-calculator
>    [args]
>    (let [...
>          house-sale-price (some-fn ... some-arg)
>          ...
>          house-depreciation (house-depreciation y house-sale-price)
>          ...
>          sell-value (sell house-sale-profit-0 rmoc mib)
>          ...]
>     (- rent sell)))
>
> This way your individual functions become really simple, expressed in  
> terms of
> their direct named inputs and outputs only, just like your PDF's  
> equations. Your
> calculator function becomes the place where the web of interconnected  
> values is
> realized through a sequence of intermediate values.
>
> You can easily test each individual function, just as you can now,  
> only without
> the overhead and syntax of those extra maps (which carry a ton of extra
> values and obscure what's happening). Individual functions are less  
> likely to
> recompute redundant values (I'm sure rent and sell both involve some  
> common
> terms), and that avoidance doesn't involve jamming intermediate values  
> into the
> argument map.
>
> More interestingly, this means you can build calculators for different  
> things
> (maybe comparing the tax advantages of different mortgages and  
> depreciation
> tricks...) by using the same functions. I don't know if that's  
> possible with
> the map approach as you've written it.
>
> What you've done with the argument map approach is essentially to use  
> a map as
> an object: the map is a bunch of fields, your derived-args function is a
> constructor (which sets up the derived members), and your individual  
> functions
> are methods on that object. It's OO with a mask.
>
> Now, OO is sometimes the right way to do things, but if you really  
> want to see
> whether a more functional approach has advantages in this situation,  
> you should
> consider whether you can invert things a little.
>
> Just a thought.

-- 
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to [email protected]
Note that posts from new members are moderated - please be patient with your 
first post.
To unsubscribe from this group, send email to
[email protected]
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en

Reply via email to