Thursday, 24 September 2020

(practical-python->racket 2.1 Datatypes and Data Structures)

 2.1 Datatypes and Data Structures

This is quite a short section that introduces Python's tuples and dictionaries. The exercises are all designed to be run in the Python console. I have included my solutions to the exercises in the datastructures.rkt file.

Primitive Datatypes

The three datatypes referred to as primitive (integers, floating point numbers and strings(text) and their Racket equivalents were covered in the introduction.

None Type

There is quite a difference Python's none type and Racket's. Python's is called None and is an object, Racket's is called null and is a constant that is defined to be an empty list. Python's None is used for optional or missing values and evaluates as false in conditional statements. As far as I can tell, null is used  much less frequently in Racket. It evaluates to true in conditional expressions.

Data Structures

Two data structures (or collections) are introduced in this section, tuples and dictionaries.

Tuples

A Python tuple is bascially an immutable list of values, exactly the same as a Racket list. Python tuples use the same syntax as Python lists for accessing individual elements. They tend to be used in lieu of structures by Python programmers though Python's named tuples are more akin to Racket's structures.

Racket doesn't seem to have anything like Python's tuple packing and unpacking built in. Unpacking is assigning the values of a tuple to multiple variables in a single statement. That could well be because having lots of individually named pieces of data isn't at all common in Racket.

Dictionaries

A Python dictionary can be described as an associative array or a hash table. Racket's hash tables provide its equivalent (and more). Racket also has dictionaries which appear to be at a higher-level that includes hash tables.

It seems that there are six types of hash table in Racket depending on how the hash keys are compared with each other and whether the table is immutable. The make-hash function creates mutable hash tables where the hashed keys are compared using the equal? function. I am using "make-hash" hash tables to compare with Python's dictionaries.

Racket allows more types of values as keys in a hash then Python. For example, in Racket you can use a list as a hash key but you can't in Python.

There is no simplified literal syntax to create a mutable hash tables in Racket that is equivalent to Python's JSON-like Dictionary syntax. (Though there is for Racket's immutable hash tables.)

So the equivalent of Python's 

    s = {

        'name': 'GOOG',

        'shares': 100,

        'price': 490.1

    }

is

    (define s (make-hash))

    (hash-set! s "name" "GOOG")

    (hash-set! s "shares" 100)

    (hash-set! s "price" 490.1)

Python uses a typical [] array notation to access individual dictionary modules such as:

  s['price'] 

The equivalent in Racket requires a call to the hash-ref function:

  (hash-ref s "price")

Python uses the same [] syntax to modify the value stored for a key, Racket uses the hash-set! function (as seen above).

Python has a del function which can be used to remove an entry from a dictionary, the equivalent in Racket is the hash-remove! function.

Exercises

The exercises are based on simple processing of data stored in csv format. Emulating most of the Python in Racket was straight forward, with the obvious exception of tuple unpacking.

There were a couple of things that are worthy of note. The first is that Python dictionaries are sequenced in the order that the keys were added. Racket's hashes are unordered.

Secondly, Racket's hash-keys function provides a simple list of the keys whereas Python's keys provides a dict_keys object which is dynamically updated if the dictionary it was created has keys subsequently added or removed.

Next up, 2.2 Containers

 


Monday, 17 August 2020

(practical-python->racket 1.7 functions)

I was expecting some difficulty in "translating" even the introductory section on functions from Python to Racket. I have only written basic functions with basic signatures in Racket so far. I have used functions which accept a variable number of parameters and those which have named parameters but I haven't write any as yet.

I was relieved when I went through the introduction to Functions in Practical Python Programming to find the it only introduced basic functions with a single parameter. It did also include error trapping and handling command line arguments so I did have some learning to do.

As calling functions recursively in the main looping paradigm in Racket, I've already written a number of functions in translating previous examples and exercises, mainly to replicate Python's for loops.

The section starts by introducing custom functions (i.e. functions defined in the program). Python Docstrings and how they would be displayed in the console by using the builtin help() function are already introduced at this early stage. As far as I know, Racket doesn't include such builtin function documentation out of the box.

The simple example of a Python function is:

def sumcount(n):

    '''

    Returns the sum of the first n integers

    '''

    total = 0

    while n > 0:

        total += n

        n -= 1

    return total

In my translation, I used an "inner" function to perform the loop:

; Calculate the sum of the first n integers

(define (sumcount n)

  (define (sc n total)

    (cond

      [(= n 0) total]

      [else (sc (- n 1) (+ total n))]))

  (sc n 0))

Whilst the Racket is the same number of lines of code as the Python, I do find it more difficult to take it in at a glance.

This is followed by two examples of Python library functions. From my point of view, both Python and Racket have three levels of function libraries - built-in functions, standard library functions and external library functions. The builtin and standard library functions are distributed as part of the language, external libraries have to be separately downloaded and installed. The distinction that in my mental model between builtin functions and library functions is that builtin functions can be used without the need to tell the language that you want to use them. For example, you don't need to import the Python print function.

(Actually, Racket consists of many different languages which will have different standard libraries and even different syntax. I am sticking to the core #lang racket language in this project.) 

The first Python library function mentioned is sqrt which is part of the Python math library:

import math

x = math.sqrt(10)

The Racket sqrt function is "builtin" and doesn't need to be required before it is used.

(sqrt x)

The second Python library function is request from the standard urllib:

import urllib.request

u = urllib.request.urlopen('http://www.python.org/')

data = u.read()

Racket also requires access to a standard library for HTTP support. The Racket standard library has a huge range of options for working with HTTP. (Probably even more than Python.) This is the neatest solution that I found:

(require net/url)

(call/input-url (string->url "https://racket-lang.org")

                  get-pure-port

                  port->string)

The introduction of errors in Practical Python Programming is based on attempting to convert a non-numeric string to an integer. Doing so causes Python to raise an exeception. Racket's equivalent, string->number, simply returns false in such cases. To emulate the Python example, I attempted to convert 'N/A.

Python console:

>>> int('N/A')

Traceback (most recent call last):

File "<stdin>", line 1, in <module>

ValueError: invalid literal for int() with base 10: 'N/A' 

Racket Console:

> (string->number 'N/A)

; string->number: contract violation

;   expected: string?

;   given: 'N/A

; [,bt for context]

Handling errors in Python and Racket seemed to be very similar to me. The main difference is the order of the code. In Python, error handling follows the "protected" code. In Racket, error handling precedes the "protected" code. I do find the Python code more easy to read at a glance though. (However from what I learnt from How To Design Programs, the readability of Racket programs can be greatly improved by wrapping code in well-named functions.)

Python

    try:

        shares = int(fields[1])

    except ValueError:

        print("Couldn't parse", line)

Racket:

 (with-handlers ([exn:fail:contract?

                  (lambda (err)

                  (display "Couldn't Parse ")

                  (displayln field))])

 (string->number field))

Raising errors is easy in both languages though in Python you must always specify the type of error (by providing an instance of the specific error). In Racket, you can either raise an unclassified error or a specific type of error. This latter requires calling a specific function such as raise-range error.

Python

    raise RuntimeError('What a kerfuffle')

The exercises start with revising how to define a simple function and calling it from the Python console. Here's my Racket version.

> (define (greeting name)

    (displayln (string-append "Hello " name)))

> (greeting "Guido")

Hello Guido

> (greeting "Paula")

Hello Paula

The remaining exercises involve converting the portfolio costing program from the files exercise into a function, adding error handling, using a library program to process the csv file and finally turning it into a command line program.

The first step is to take the function written during the exercises for 1.6 Files which totalled the cost of a stock portfolio stored in a csv file and turn it into a function. The original Python function used a for ... in loop which I translated into a Racket recursive function. So I wrapped the recursive function inside another function to complete the exercise:

; portfolio cost - 1st iteration - takes a filename parameter

(define (portfolio-cost filename)

  ; pcost - an internal function to perform the "loop"

  (define (pcost rows)

    (cond

      [(empty? rows) 0]

      [else (+ (* (string->number 

                    (second (string-split (first rows) ",")))

                  (string->number 

                    (third (string-split (first rows) ","))))

               (pcost (rest rows)))]))

  (define csv-rows (file->lines filename #:mode 'text))

  (pcost (rest csv-rows)))      

The python -i command line option is used to show how to load a file (a module in Python) and access Python interactively. I didn't find this so straight forward in Racket. I'm sure it is possible but it seems to require knowledge of Racket modules which I haven't looked into yet.

The next exercise is to add error handling to the function to ignore any lines where the cost coduln't be calculated. I wrapped the cost calculation in a call of the with-handlers function. It looks quite messy and I'd guess that the Racket Way would have been to isolate the calculation into a function to keep the main code cleaner:

; portfolio cost - 2nd iteration - add error handling

(define (portfolio-cost filename)

  ; pcost - an internal function to perform the "loop"

  (define (pcost rows)

    (cond

      [(empty? rows) 0]

      [else (+ (with-handlers ([exn:fail:contract?

                               (lambda (err)

                                 (display "Couldn't Parse ")

                                 (displayln (first rows))

                                 0)])

                 (* (string->number 

                      (second (string-split (first rows) ",")))

                    (string->number 

                      (third (string-split (first rows) ",")))))

               (pcost (rest rows)))]))

  (define csv-rows (file->lines filename #:mode 'text))

  (pcost (rest csv-rows)))

The next exercise was to use a library function to parse the csv data rather than doing it "by hand". There is a csv module in Python's standard library. There is a csv parsing module available for Racket. It is not included in the standard library but is "approved" in the sense that it is included in the official Racket documentation. It only took a couple of minutes to install using Dr Racket. It did also install a couple of other libaries on which it is dependent upon.

I used the csv library only to convert the csv to a list of lists. It took away the need to split each row of the csv data. (It also would have taken care of things like embedded quotes that string-split wouldn't have handled.)

; portfolio cost - 3rd iteration - use a csv library

(require csv-reading)

(define (portfolio-cost filename)

  ; pcost - an internal function to perform the "loop"

  (define (pcost rows)

    (cond

      [(empty? rows) 0]

      [else (+ (with-handlers ([exn:fail:contract?

                               (lambda (err)

                                 (display "Couldn't Parse ")

                                 (displayln (first rows))

                                 0)])

                 (* (string->number (second (first rows)))

                    (string->number (third (first rows)))))

               (pcost (rest rows)))]))

  (define csv-rows (call-with-input-file filename csv->list))

  (pcost (rest csv-rows)))

The last exercise was to write a program that called the function with a filename passed as a command line argument. After a little searching, I found that (vector-ref (current-command-line-arguments) 0)) will provide the first command line argument. Here's the final program:

(require csv-reading)


; get the filename from the command line

(define filename (vector-ref (current-command-line-arguments) 0))


; define the function to calculate the cost of the portfolio

(define (portfolio-cost filename)

  ; pcost - an internal function to perform the "loop"

  (define (pcost rows)

    (cond

      [(empty? rows) 0]

      [else (+ (with-handlers ([exn:fail:contract?

                               (lambda (err)

                                 (display "Couldn't Parse ")

                                 (displayln (first rows))

                                 0)])

                 (* (string->number (second (first rows)))

                    (string->number (third (first rows)))))

               (pcost (rest rows)))]))

  (define csv-rows (call-with-input-file filename csv->list))

  (pcost (rest csv-rows)))

; calculate the cost of the portfolio in the file provided

(define total-cost (portfolio-cost filename))

(display "Total cost: ")

(displayln total-cost)


The final program is portfolio-cost.csv, the translation of the examples is functions.rkt.

That concludes the Introduction. Next comes Working With Data which covers many of Python's more complex datatypes. Its first section covers an introduction to data structures.


Monday, 3 August 2020

(practical-python->racket 1.6 Files)

Section 1.6 Files of Practical Python Programming follows a similar to the previous two sections. It does have one exercise that calls for a complete program but it is such a short one that I included all the examples and exercises in the single file - files.rkt

The Python examples and exercises are designed to be run from the Python console started from a specified directory. I cannot be certain from where files.rkt will be run so in the script I needed to make sure the correct filepaths, relative to the directory from which the script was started, would be used. This is a perenial banana skin for me in most languages. I found a possible solution by referring to Stack Overflow. It wasn't elegant but it did work. Thankfully I checked on the Racket Mailing List and was kindly pointed to the define-runtime-path function which, happily for me, takes care of the problem for you.

You can achieve the same in Python but, as far as I know, not quite so easily.
Racket
(define-runtime-path f "my-file.txt")
Python
f = os.path.join(os.path.dirname(__file__), 'my-file.txt')

The examples start with opening text files and reading and writing their contents into and out of strings. Translating them was relatively straight forward. The only thing that I note is that in Racket you open a file but then refer to it as a port. (Again this may be a lack of understanding on my part). This might be a little confusing to a newcomer but, having been introduced to ports in Rebol, it didn't have that effect on me.

(define f (open-input-file foo.txt #:mode 'text))
(port->string f)
(close-input-port f)

This is maybe a little academic as Racket's file->string function reads the contents of a file into a string, automatically opening and closing the file. (The same is true of Racket's write-to-file function for writing a string to a file.)

The Python examples for processing data use Python's context manager feature which automatically closes a file once it has been processed. Racket's file->string and file->lines do the same more concisely as there in no need to explicitly open the file as there is with a Python context manager.

The Python next function is used in Exercise 1.26 File Preliminaries. As the example only calls next() once, I used Racket's first and rest on a list of lines read from the file to simulate it.

I also came up with a solution that was closer to the python example:
  (define f (open-input-file Data/portfolio.csv #:mode 'text)))
  (define headers (read-line f)))   ;"remove" the headers from the port 
  (define rows (port->liines f))    ; read the remainder of the file
  (close-input-port f))
  (displayln headers))
  (for-each (lambda (r) (displayln r)) rows))

The one example that called for separate program was to calculate the cost of a share portfolio where the number of shares and their individual costs was stored in a csv file. Here is my solution, a pretty straight forward recursive function:

  (define csv-rows (file->lines Data/portfolio.csv #:mode 'text)))
  (define (pcost rows)
    (cond
      [(empty? rows) 0]
      [else (define row (string-split (first rows) ","))
            (+ (* (string->number (second row))
                  (string->number (third row)))
               (pcost (rest rows)))]))
 (display "Total cost "))
  (displayln (pcost (rest csv-rows))))

The final exercise was to open a gzipped version of a csv file. Both Python and Racket required gzip libraries to be imported/required from their standard libraries. Though neither requires an additional library to be installed. The only item of note is that the Racket gunzip function requires an output string to be passed as a parameter into which it decompressed the data. At first glance, Racket's output-strings seem similar to Python StringIO objects. I fully expect to have to find out more as I progress with translating Practical Python Programming. Here is my translation of the Python example:

  (require file/gunzip)
  (define gzip (open-input-file "Data/portfolio.csv.gz"))
  (define csv (open-output-string))
  (gunzip-through-ports gzip csv)
  (displayln (get-output-string csv))

So that wraps up the Introduction to Files. Next up is 1.7 Functions which I fully expect to require some study on my part before I'll be able to come up with a translation.


Friday, 24 July 2020

(practical-python->racket 1.5 Lists)

The format on the section 1.5 Lists in Practical Python Programming is similar to the section on Strings. Again, I have created a single file, lists.rkt, that covers both the examples and exercises. 

Before starting on the noticeable differences (to me), a quick reminder to say that I'm in the process of learning Racket and my translations are highly likely not to be the best. If you can suggest better ones, please leave a comment.

The one big difference that I noticed is that lists are mutable in Python but immutable in Racket. (Though Racket does have a separate mutable list datatype.) That apart, there seemed to be less differences between Python and Racket Lists than there is between their strings.

Python has an insert method which allows an item to be inserted at specified point in the list. I was able to translate by using the Racket take and drop functions:
  Python
  names.insert(2, "Aretha"))
  Racket
  (append (take names 2) '("Aretha") (drop names 2))

As with Strings, Python supports supplying a negative index to indicate an index from the end of the list. This can be easily be emulated by using the length function to calculate the desired index. For lists, but not strings, Racket has the last function which makes it easy to get the last item in a list.

Translating in function required using Racket's index-of function and checking whether it returned a number or not.
Python
  'Elwood' in names
Racket
  (number? (index-of names "Elwood"))

It seems that it is only possible to sort lists in both Python and Racket if they have the same type of data.

Python:
>>> [1, "b", 3].sort()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: '<' not supported between instances of 'str' and 'int'

Racket:
> (sort '(1 "b" 3) >)
; >: contract violation
;   expected: real?
;   given: "b"
;   argument position: 1st
; [,bt for context]

In summary, I found translating the contents of Lists quite a bit easier than I did with Strings.

Next up 1.6 Files

Tuesday, 21 July 2020

(practical-python->racket 1.4 Strings)

The format of the Practical Python Programming 1.4 Strings is a little different from Section 1.3 Numbers. The exercises are designed to be entered into the Python console rather than complete, albeit short, programs. I have created a single racket program, strings.rkt, that covers both the examples and the exercises.

Python seems to have a plehtora of string literal types which are deliminated between ' ', " ", ''' ''', """ """, f' ', f" ", r' ', r" ", b' ' and b" ". In Python there is no difference between a string deliminated with ' ' or one deliminated with " ". Both are single-lines strings that expand escaped characters. ''' and """ are used for multi-line strings. The other options are r strings (raw strings), b strings (binary string) and f strings (format strings).

As far as I know racket has two string literal forms, one deliminated with " ", the other binary strings deliminated with #" ". It was a pleasant surprise to find that Racket strings are multi-line.

Translating most of the examples and exercises from Pyhton to Racket was quite straightforward. I'll only mention the few that weren't.

In Python you can specify negative indices which count back from the end of the string. It's a nice convenience but isn't that difficult to manage without by calculating the index through reference to the string length.

Python's string replication using the * operator (hello_5 = "Hello" * 5) proved to be a little more challenging. In trying to come up with a solution, I stumbled across Racket's for loop function. It was quite a suprise to find a for loop in Racket. Using the for function and set!, I was able to come up with a translation to Racket.

(define sss "Hello")
(define original-sss (string-copy sss))
(for ([i (in-range 1 5)])
     (set! sss (string-append sss original-sss)))
(displayln sss)

My Racket isn't anywhere near as concise as the Python though it could form the basis for a crude string-replicate function. Personally, I feel that such a function would be so rarely used that it wouldn't be worth writing one.

Python's String find and index methods return the index of the start of a substring found within a string. My first attempt to translate them was (index-of (string->list s) t). However this would only work to find a single character. After a litle more searching, I found that by using a regular expression I could duplicate the Python methods - (car (car (regexp-match-positions s t))). (The car-car is needed as the Racket regexp-match-positions returns a list of pairs of start and end positions.) 

The regular expression to translate the Python rfind and rindex string methods which search backwards from the end of a string is a little more complicated - (car (last (regexp-match-positions (regexp (string-append ".*(" t ").*$")) s)))). It is perhaps easier to understand if split into two expressions:

(define reggie (regexp (string-append ".*(" t ").*$")))
(car (last (regexp-match-positions t s)))

To emulate the Python isalpha String method, I needed to resort to a regular expression again as I couldn't find anything similar in the Racket standard library. At first this seemed easy (regexp-match? #px"^[a-zA-Z]+$" s) worked well against English strings. I then remembered that Python has pretty good Unicode support. I checked and confirmed that Python's isalpha recognises more than a to z as "alpha". I was struggling to find a solution in Racket so I posted a message on the Racket Mailing List. Ryan Culpepper of the Racket Team not only very kindly explained how characters are classified in Unicode, he also showed me how this information could be accessed in Racket and how there are special regular expression character classes in Racket that can be used to process the Unicode character classification. 

If that wasn't enough he also pointed out the need to normalise Unicode code points in "composed" form when using those special reqex character classes. (regexp-match? #px"^\p{L}+$" (string-normalize-nfc s))) is my resulting equivalent of Python's isalpha.

One significant difference between Python and Racket strings, which was a little surprising to me, is that whilst Python strings are immutable Racket has both immutable and mutable strings. A racket string created using "" enclosed string literals is immutable, a string created with the string function is mutable. This short Racket repl session shows the difference:

> (define immutable "Hallo")
> (string-set! immutable 1 #\e)
; string-set!: contract violation
;   expected: (and/c string? (not/c immutable?))
;   given: "Hallo"
;   argument position: 1st
; [,bt for context]
> (define mutable (string #\H #\a #\l #\l #\o))
> (string-set! mutable 1 #\e)
> mutable
"Hello"

As far as I can tell Racket doesn't have any string interpolation features like Python's f strings. It was pretty straightforward to translate the f string example but much more wordy.
  Python:
    f'{shares} shares of {name} at $(price:0.2f}'
  Racket:
     (displayln
       (string-append
         (~a shares)
         " shares of "
         name
         " at $"
         (real->decimal-string price 2)))

Both Python and Racket are considered "Batteries included" languages. I'm worried that my translations of Python to Racket might give the impression that Python has the bigger batteries. So to ensure a little better balance on that score, Racket not only has equivalents of Python's lower() and upper() string methods but also has titlecase and foldcase options. 

Finally these are very basic String examples, as to be expected in a course of the nature of Practical Python Programming. They do not cover any of the nuances of supporting Unicode. I have been led to believe that whilst Python has pretty good Unicode support it falls down a little as it doesn't handle different locales well. As I understand Racket does fully support different locales.

Next, 1.5 Lists
    

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.

Thursday, 9 July 2020

(practical-python->racket exercise 1.8 extra payments)

The second exercise takes the first a step further by calculating the affect of making an additional monthly payment of 1,000 dollars in the first year of the loan. Having come up with two versions of the mortgage payment example, which should I use for this example? I felt I'd get the most out of the exercise if I again came up with "HTDP" and "practical" versions.

Starting with the "HTDP" version, I realised that there was one improvement that I could make before I even started on the improvements. I added some constant definitions to add a little clarity:

; constants
(define LOAN_AMOUNT 500000.00)
(define INTEREST_RATE 0.05)
(define MONTHLY_PAYMENT 2684.11)

and changed the definition of "daves-mortgage":

(define daves-mortgage
  (make-mortgage LOAN INT_RATE MTHLY_PAYMENT 0 0.00))


This trivial change put me in a "refactoring" frame of mind. The next step I took was to wrap the superficially direct use of the mortgage-payment field in a function. This turned out to be a little heavier than I expected as I needed to pass the whole mortgage structure to the wrapper function (so that I can later calculate additional payment amounts.

This is the simple wrapper function:
(check-within (payment (make-mortgage 1000 .10 100 0.00 0.00))
                       100
                       1e-5)
(define (payment mortgage)
  (mortgage-payment mortgage))


When I came to actually make the changes to reflect the additional payments, there was a nuance due to the way that I was feeding the data back into the pay function recursively. I only needed to increase the payment by 1,000 dollars in the first month and then decrease it by 1,000 dollars in the 13th month. This is the resulting payment function:

(define (payment mortgage)
  (cond
    [(= (mortgage-num-payments mortgage) 0)
     (+ (mortgage-payment mortgage) 1000)]
    [(= (mortgage-num-payments mortgage) 12)
     (- (mortgage-payment mortgage) 1000)]
    [else (mortgage-payment mortgage)]))


The only other significant change was to return both the total paid and the number of months from the main pay function. I choose to return the two values as a pair. (A datatype I only learnt about when reading the Data Types chapter of The Racket Guide). I then used car and cdr to extract the two items from the pair. (It made me feel like a real lisp programmer ;-) ).


One area where my practical knowldege of Racket is severely lacking is formatting simple output. I really need to learn the Racker equivalent of Python's print(f'The number is {i}') or even print(i ,j).

Making the changes to the "practical" version was straightforward. I even remembered to use when instead of if. (if requires expressions for both the "then" and "else" cases.

The final programs are ex_1_8_extra_payments.rkt and ex_1_8_extra_payments_2.rkt

Now on to the rest of the exercises.