Wednesday, 11 November 2020
(practical-python->racket wait)
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)
Friday, 24 July 2020
(practical-python->racket 1.5 Lists)
Tuesday, 21 July 2020
(practical-python->racket 1.4 Strings)
Monday, 13 July 2020
(practical-python->racket exercises 1.9 1.10 1.11)
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.
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')
#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)
; 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 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))
(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)]))
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.