Monday 13 July 2020

(practical-python->racket exercises 1.9 1.10 1.11)

The next step in the mortgage exercise is to "parameterise" the start month, end month and additional payment amount. It was fairly trivial to update the "practical" version to do so. The code is at ex_1_9_extra_payment_calculator_2.rkt.


To make the changes to the "HTDP" versions, I extended the main data structure to include the new data items. I had to be careful to adjust the number of payments before calculating the payment for a month. The code is at ex_1_9_extra_payment_calculator.rkt.



The penultimate step in the mortgage exercise is to display a table showing the total paid and outstanding balance for each month of the loan. For me, the sensible way to do this was to update the mortgage data, display the values and then call the pay function recursively with the updated mortgage. It sounded easy but I wasn't certain how to go about using a "local" variable in a function.  In the two parts of HTDP that I studied there was little discussion of the scope of bindings, though the local function was introduced. I had read somewhere that lexical scoping applied in Racket. So I tried out this simple code:

> (define a 1)
> (define (f) (define a 2) (displayln a))
> (displayln a)
1
> (f)
2
> (displayln a)

It turned out that it's easy to define a local binding in Racket. The code is at ex_1_10_making_a_table.rkt


However, I'm now puzzled as to why HTDP introduced the local function and didn't mention that defining a binding inside a function would result in it being local to the function.

Adding the code to display the table was very straightforward in the "practical" - ex_1_10_making_a table_2.rkt

The final step in the mortgage exercise is to eliminate the overpayment in the final month. This required the separation of the interest calculation and payment when working out the new mortgage balance each month. Then the monthly payment could be calculated as the minimum of the normal payment amount and the outstanding balance. This was straight forward in the "practical" version with an additional when expression.  (ex_1_11_mortgage_2.rkt).

The "HTDP" version required a couple of additional local bindings to get this done. (ex_1_11_mortgage.rkt).

To complete the introductory section on Numbers, lets compare the "practical" Python and Racket solutions. (This is my Python solution not that supplied by David Beazley).

Python:
# mortgage.py
#
# Exercise 1.11

principal = 500000.0
rate = 0.05
payment = 2684.11
total_paid = 0.0
number_payments = 0
extra_payment_start_month = 61
extra_payment_end_month = 108
extra_payment = 1000

while principal > 0:
    principal = principal * (1+rate/12)
    payment = min(payment, principal)
    principal = principal - payment
    number_payments += 1
    total_paid += payment
    if  number_payments >= extra_payment_start_month and \
        number_payments <= extra_payment_end_month:
        total_paid += extra_payment
        principal -= extra_payment 
    print(number_payments,
          round(total_paid, ndigits=2),
          round(principal, ndigits=2))
    
print('Total paid', round(total_paid, ndigits=2), end=' ')
print('in', number_payments, 'months')

Racket:
#lang racket

(define principal 500000.0)
(define rate 0.05)
(define payment 2684.11)
(define total-paid 0.0)
(define number-months 0)
(define extra-payment-start-month 61)
(define extra-payment-end-month 108)
(define extra-payment 1000)

(define (pay)
  (cond
    [(<= principal 0) total-paid]
    [else (set! number-months (add1 number-months))
          (set! principal (* principal (+ 1 (/ rate 12))))
          (set! payment (min payment principal))
          (set! principal (- principal payment))
          (set! total-paid (+ total-paid payment))
          (when (and (>= number-months extra-payment-start-month)
                     (<= number-months extra-payment-end-month))
            (set! total-paid (+ total-paid extra-payment))
            (set! principal (- principal extra-payment)))
          (display number-months)
          (display " ")
          (display total-paid)
          (display " ")
          (displayln principal)
          (pay)]))

(display "Total paid ")
(display (pay))
(display " in ")
(display number-months)

(displayln " months.")

The biggest conceptual difference to me is the need to employ a recursive function in Racket to repeat the calculation. The other noticeable difference at this stage is that the most people would consider the Python to be easier to read. (Though the need to use a continuation mark suggests frustration ahead for the unaware.)

Next up 1.4 Strings.

No comments: