In the last part, I implemented a couple of primitive functions so that they can be applied in the following chapters. The second chapter of the book, is titled “Do it again, and again, and again…”. The title already hints that readers will deal with repetitions throughout the chapter.
First of everything is the definition of lat
. This is defined as a list of atoms. So practically it is a subset of a normal list (which can be constructed with any s-expressions). The first actual function to build, is to check if each of the elements are atoms. Most of the primitives are now stored in primitives.py, hence the import statement. Also there’s no input type check anymore, and it is assumed that all the input are of the correct type unless otherwise specified.
from primitives import * def test_islat(): test_cases = ((('Jack', 'Sprat', 'could', 'eat', 'no', 'chicken', 'fat'), True), ((('Jack', ), 'Sprat', 'could', 'eat', 'no', 'chicken', 'fat'), False), (('Jack', ('Sprat', 'could'), 'eat', 'no', 'chicken', 'fat'), False), ((), True), (((('tomato', 'sauce'),), (('bean', ), 'sauce'), ('and', (('flying', ), ), 'sauce')), False)) for l, expected in test_cases: result = islat(l) assert result == expected def islat(l): return (True if isnull(l) else islat(cdr(l)) if isatom(car(l)) else False)
Not sure the reason behind the weird order of ternary operator in Python, however it works just fine.
Next we are given another problem, which is to write a function to tell if an atom is a member of a given list.
def test_ismember(): test_cases = (('tea', ('coffee', 'tea', 'or', 'milk'), True), ('poached', ('fried', 'eggs', 'and', 'scrambled', 'eggs'), False), ('meat', ('mashed', 'potatoes', 'and', 'meat', 'gravy'), True), ('liver', ('bagels', 'and', 'lox'), False)) for atom, lat, expected in test_cases: result = ismember(atom, lat) assert result == expected def ismember(a, lat): return (False if isnull(lat) else or_(iseq(car(lat), a), ismember(a, cdr(lat))))
So there’s a common pattern that can be observed here. An exit condition is always defined first, which makes sense because otherwise the function will be looping infinitely. In these cases, the exit point is whenever the list of atom is fully consumed. Both of the function also cares about the first atom in the list, and defer the rest to a recursive copy of itself to figure out.
Then we reach the end of second chapter (The book itself spent most of the pages tracing examples to each line of the code).