Sean and Richard perhaps I can address both of your mails in a single
go. Here is an example of one of the functions from my calculator:

(defn tax_deductible_expenses
        "The total expenses incurred in business month m that are deductible
from federal income tax"
        [m]
        (let
                [prepHouse (inflate *Prep_House_0* m)
                 fixedMonthlyCost (if (= m (months_in_business)) 0
(fixed_monthly_cost m))]
                (condp = (house_state m)
                        :ForLease (+ fixedMonthlyCost (if (= 
(month_in_rental_cycle m) 0)
prepHouse 0))
                        :Leased (+ fixedMonthlyCost (* (rental_income m) (+
*Management_Fee* (if (= (month_in_rental_cycle m)
*Months_To_Find_Tenant*) *Tenant_Finding_Fee* 0))))
                        :ForSale (+ fixedMonthlyCost (if (= m 
(months_actively_renting))
prepHouse 0))
                        :Sold (house_sales_expenses m))))

Right now the function takes a single argument, m which is the month
that tax deductible expenses are being calculated for. All the other
values it needs are "constants" (e.g. either values provided at the
start by the user or derived values).

Now please look at a test I wrote to make sure the function does what
I think it does:

(deftest tax_deductible_expenses_basic
  (binding
    [*Months_To_Find_Tenant* 5
     *Months_In_Lease* 5
     *Lease_Cycles* 1
     *House_Sales_Price_0* 300
     *Monthly_Inflation_Rate* 0.002
     *Buying_Agent_Fee_Type* :Percentage
     *Buying_Agent_Fee_Number* 0.05
     *Selling_Agent_Fee_Type* :FlatFee
     *Selling_Agent_Fee_Number* 1000
     *Other_Sales_Fee_0* 100
     *Excise_Tax* 0.5
     *Original_Loan_Amount* 10000
     *Monthly_Loan_Interest* (/ 0.05 12)
     *Months_In_Loan* (* 10 12)
     *Loan_Month_At_Start* 3
     *Prep_House_0* 100
     *Management_Fee* 2
     *Tenant_Finding_Fee* 0.5
     *Months_To_Sell* 3]
     (is (= (tax_deductible_expenses 0) (+ (fixed_monthly_cost 0)
100)))
     (is (= (tax_deductible_expenses 1) (fixed_monthly_cost 1)))
     (is (= (tax_deductible_expenses 5) (+ (fixed_monthly_cost 5) (*
(rental_income 5) 2.5))))
     (is (= (tax_deductible_expenses 7) (+ (fixed_monthly_cost 7) (*
(rental_income 7) 2))))
     (is (= (tax_deductible_expenses 10) (+ (fixed_monthly_cost 10)
(inflate 100 10))))
     (is (= (tax_deductible_expenses 12) (fixed_monthly_cost 12)))
     (is (= (tax_deductible_expenses 13) (house_sales_expenses 13)))))

For the method tax_deductible_expenses to run it ends up requiring 19
user defined values. That's a whole lot of arguments to pass into a
function. Obviously I could wrap them up in a StructMap but then I get
expressions like (+ 1 (args :B)) which doesn't seem much better than
(+1 (B)).

The issue I'm really trying to put my finger on is - these arguments
really and truly are 'constants' within the context of the calculator.
But they are constants whose values are not known as compile time but
rather are known at run time. Does Clojure have a way to express a
'late bound' constant or is the 'right' solution to pass around 19+
arguments to functions or passing around StructMaps or making
everything into thunks?

   Thanks!

         Yaron

On Feb 15, 1:33 pm, Richard Newman <holyg...@gmail.com> wrote:
> > 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).
>
> In Java, everything's an object, so you go about this by defining some  
> class. All of its private members, its constructors, and its accessors  
> are there to support one thing: should I sell or rent my home?
>
> That is, rather than saying something like:
>
> should-i-sell given that:
>    current-home-price = 100,000
>    current-interest-rate = 5.2%
>    ...
>
> you say
>
> HomeCalculator c = new HomeCalculator(100000, 5.2, ...);
> boolean shouldSell = c.shouldSell();
>
> and the logic is tied up in the shouldSell method definition.
>
> When someone calls .setA(...), you have to recompute B, or you have to  
> make sure that B is dynamically computed in .getB().
>
> That's not how you do things in a functional programming language. You  
> don't define global variables "B" and "A". Define functions that  
> compute things; thread them together; and then push your values in the  
> top.
>
> Start from the bottom up and the top down together; build a tree of  
> functions that compute what you want. For example, you might think  
> "ah, I need to figure out my monthly payment on my mortgage". That's a  
> function of the initial principal, the term, and the rate:
>
> (defn monthly-payment [principal term-in-months interest-rate]
>    ...)
>
> Then you want to figure out how much more or less you'll pay, assuming  
> a growth in rents of a certain percentage, over some number of months  
> spent living in the house. Let's start by computing a sequence of  
> rents, increasing over time:
>
> (defn monthly-rent [starting-value monthly-increase]
>    (lazy-seq
>      starting-value
>      (monthly-rent (* (+ 1 monthly-percentage-increase) starting-
> value) monthly-increase)))
>
> then we want to weigh these against each other:
>
> (defn rent-cost-over-time [starting-value monthly-increase months]
>    (reduce + (take months (monthly-rent starting-value monthly-
> increase)))
>
> (defn mortgage-cost-over-time [principal term-in-months interest-rate  
> months]
>    (...))
>
> You get the idea: you're building a library of *pure* functions, each  
> of which does one thing to some inputs, and might rely on the others.
>
> Now you're ready to phrase your question as a function:
>
> (defn should-i-sell
>    [initial-mortgage-principal
>     monthly-interest-rate
>     months-already-paid-into-house
>     ...]
>    )
>
> If you want to use keywords to denote named arguments, you can do that:
>
> (defn should-i-sell
>    [& args]
>    (let [{:keys [initial-mortgage-principal ...]} (apply hash-map args)]
>      ...)
>
> No bindings. No global definitions. No redefinition. No state. User  
> input comes in to your function and is passed through other functions.  
> Eventually you get a value. If the user input changes, re-run the  
> function. Dataflow programming is overkill for what you're doing.
>
> You don't have "primary values" and "dependent values": you have  
> function inputs, and functions that compute values from inputs.
>
> Hope that helps...
>
> -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