Thursday 2 July 2020

(practical-python->racket exercise 1.7 mortgage)

The first exercise is to calculate the total payments taken to pay off a 30-year fixed rate mortgage. David Beazley provides an initial solution to get his students started.


I started to think through how best to convert David's example into Racket. Almost without thinking, I fell back on the techniques covered in the first parts of How To Design Programs (HTDP). I designed a structure to hold the data and then wrote a recursive function to process it. (How else to replicate a while loop in Racket?) I added a couple of simple tests to check it was working. The program gives the same result as its Python equivalent but with its structure and function definitions, it lacks the immediacy of it:

#lang racket
(require test-engine/racket-tests)

; data definitions
(define-struct mortgage 
  [balance rate payment num-payments total-paid])

; data
(define daves-mortgage
  (make-mortgage 500000.00 0.05 2684.11 0.00 0.00))

; functions
(check-within (apply-interest 100 1.20) 110.0 1e-5)
(define (apply-interest balance rate)
  (* balance (add1 (/ rate 12))))

(check-within (pay daves-mortgage) 966279.6 1e-5)
(define (pay mortgage)
  (cond
    [(<= (mortgage-balance mortgage) 0) 
     (mortgage-total-paid mortgage)]
    [else (pay (make-mortgage
                (- (apply-interest
                    (mortgage-balance mortgage)
                    (mortgage-rate mortgage))      
                (mortgage-payment mortgage))
                (mortgage-rate mortgage)
                (mortgage-payment mortgage)
                (add1
                 (mortgage-num-payments mortgage))
                (+ (mortgage-total-paid mortgage)
                (mortgage-payment mortgage))))]))

(test)

; The "program"
(display "Total paid ")
(displayln (pay daves-mortgage))

So I set about trying to better emulate the Python version. In my first attempt, I passed indvidual values to the recursive function instead of a structure. It looked a little more like the Python version and was roughly half the length of the "HTDP" attempt (in terms of lines of code). But it still didn't feel very "practical".

The only way I could think of to make the code look more like the Python was to use "global, mutable variables" (in the same way that the Python does). In the part of HTDP that I had studied, there was never any mention of changing data, everything was immutable. I had somewhere seen "set" used in Racket code so I looked it up in the Racket Reference and did my worst. The code does look much more more like the Python but must rank as some of the worst Racket code ever written:

#lang racket

(define principal 500000.0)
(define rate 0.05)
(define payment 2684.11)
(define total_paid 0.0)

(define (pay)
  (cond
    [(<= principal 0) total_paid]
    [else (set! principal 
                (- (* principal (+ 1 (/ rate 12)))
                   payment))
          (set! total_paid (+ total_paid payment))
          (pay)]))

(display "Total paid ")
(displayln (pay))

No comments: