Lab 9: Scheme

Due at 11:59pm on 04/02/2015.

Starter Files

Download lab09.zip. Inside the archive, you will find starter files for the questions in this lab, along with a copy of the OK autograder.

Submission

By the end of this lab, you should have submitted the lab with python3 ok --submit. You may submit more than once before the deadline; only the final submission will be graded.

Table of Contents

Downloading Scheme

Our course uses a custom version of Scheme (which you will build for project 4). You can download it here (it is already included with the starter ZIP archive). To start it, use the following command:

python3 scheme

To close the Scheme interpreter, you can type (exit).

Scheme is a famous functional programming language from the 1970s. It is a dialect of Lisp (which stands for LISt Processing). The first observation most people make is the unique syntax, which uses Polish-prefix notation and (often many) nested parentheses. (See: http://xkcd.com/297/). Scheme features first-class functions and optimized tail-recursion, which were relatively new features at the time.

Basics

Question 1: Primitives

Let's take a look at the primitives in Scheme. Open up the Scheme interpreter in your terminal with python3 scheme to see what Scheme would print.

scm> 1          ; Anything following a ';' is a comment
______
1
scm> 1.0
______
1.0
scm> -27
______
-27
scm> #t
______
True
scm> #f
______
False
scm> "A string"
______
"A string"
scm> 'symbol
______
symbol
scm> nil
______
()

Question 2: Expressions

Of course, what kind of programming language would Scheme be if it didn't have any functions? Scheme uses Polish prefix notation, where the operator comes before the operands. For example, to evaluate 3 + 4, we would type into the Scheme interpreter:

(+ 3 4)

Notice that to call a function we need to enclose it in parentheses, with its arguments following. Be careful about this, as while in Python an extra set of parentheses won't hurt, in Scheme, it will interpret the parentheses as a function call. Evaluating (3) results in an error because Scheme tries to call a function called 3 that takes no arguments, which would result in an error.

Let's familiarize ourselves with some of the built-in functions in Scheme. Try out the following in the interpreter:

scm> (+ 3 5)
______
8
scm> (- 10 4)
______
6
scm> (* 7 6)
______
42
scm> (/ 28 2)
______
14
scm> (+ 1 2 3 4 5 6 7 8 9)
______
45
scm> (quotient 29 5)
______
5
scm> (remainder 29 5)
______
4
scm> (= 1 3)
______
False
scm> (> 1 3)
______
False
scm> (< 1 3)
______
True
scm> (or #t #f)
______
True
scm> (and #t #t)
______
True
scm> (and #t #f (/ 1 0)) ; Short-Circuiting
______
False
scm> (not #t)
______
False
scm> (define x 3)               ; Defining Variables
______
x
scm> x
______
3
scm> (define y (+ x 4))
______
y
scm> y
______
7

Defining functions

To write a program, we need to write functions, so let's define one. The syntax for defining a function in Scheme is:

(define ([name] [args])
        [body])

For example, this is how we would define the double function:

(define (double x)
    (+ x x))

Question 3: Cube

Lets try it out! Define a function which cubes an input.

(define (cube x)
'YOUR-CODE-HERE
(* x x x)
)

To load a file into Scheme, you can use the following command (replace name_of_file with the name of your file):

python3 scheme -load name_of_file.scm

This is like python3 -i name_of_file.py.

As with other labs, you can run OK to test your function by providing the name of the function. This time, however, you will first have to unlock the tests.

python3 ok -q cube -u
python3 ok -q cube

Control structures

So far, we can't do much in our functions so let's introduce control statements to allow our functions to do more complex operations! The if-statement has the following format:

(if [condition]
    [true_result]
    [false_result])

For example, the following code written in Scheme and Python are equivalent:

; Scheme
(if (> x 3)
    1
    2)

# Python
if x > 3:
    1
else:
    2

Notice that the if-statement has no elif case. If want to have multiple cases with the if-statement, you would need multiple branched if-statements:

; Scheme
(if (< x 0)
    'negative           ; returns the symbol negative
    (if (= x 0)
        'zero           ; returns the symbol zero
        'positive       ; returns the symbol positive
    )
)

# Python Equivalent
if x < 0:
    print('negative')
else:
    if x == 0:
        print('zero')
    else:
        print('positive')

Question 4: Over or under

Define a function over-or-under which takes in an x and a y and returns the the following:

(define (over-or-under x y)
'YOUR-CODE-HERE
(if (= x y) 0 (if (> x y) 1 -1))
)

Using Conditionals

These nested 'if' statements get messy as more cases are needed, so alternatively, we also have the cond statement, which has a different syntax:

(cond ([condition_1] [result_1])
      ([condition_2] [result_2])
        ...
      ([condition_n] [result_n])
      (else [result_else]))                ; 'else' is optional

With this, we can write control statements with multiple cases neatly and without needing branching if-statements.

(cond ((and (> x 0) (= (modulo x 2) 0)) 'positive-even-integer)
      ((and (> x 0) (= (modulo x 2) 1)) 'positive-odd-integer)
      ((= x 0) 'zero)
      ((and (< x 0) (= (modulo x 2) 0)) 'negative-even-integer)
      ((and (< x 0) (= (modulo x 2) 1)) 'negative-odd-integer))

Lambdas

Ah yes, you thought you were safe, but we can also write lambda functions in Scheme!

As noted above, the syntax for defining a procedure in Scheme is:

(define ([name] [args])
        [body]
)

Defining a lambda has a slightly different syntax, as follows:

(lambda ([args])
        [body]
)

Notice how the only difference is the lack of function name. You can create and call a lambda procedure in Scheme as follows:

; defining a lambda
(lambda (x) (+ x 3))
; calling a lambda
((lambda (x) (+ x 3)) 7)

Question 5: make-adder

Write the procedure make-adder which takes in an initial number, num, and then returns a procedure. This returned procedure takes in a number x and returns the result of x + num.

(define (make-adder num)
'YOUR-CODE-HERE
(lambda (x) (+ x num))
)

Lists

In Scheme, lists are composed similarly to how Linked Lists that we had worked with in Python were implemented. Lists are made up of pairs, which can point to two objects. To create a pair, we use the cons function, which takes two arguments:

scm> (define a (cons 3 5))
a
scm> a
(3 . 5)

Note the dot between the 3 and 5. The dot indicates that this is a pair, rather than a sequence (as you'll see in a bit).

To retrieve a value from a pair, we use the car and cdr functions to retrieve the first and second elements in the pair.

scm> (car a)
3
scm> (cdr a)
5

Look familiar yet? Like how Linked Lists are formed, lists in Scheme are formed by having the first element of a pair be the first element of the list, and the second element of the pair point to another pair containing the rest of list, or to nil to signify the end of the list. For example, the sequence (1, 2, 3) can be represented in Scheme with the following line:

scm> (define a (cons 1 (cons 2 (cons 3 nil))))

which creates the following object in Scheme:

linked list

We can then of course retrieve values from our list with the car and cdr function.

scm> a
(1 2 3)
scm> (car a)
1
scm> (cdr a)
(2 3)
scm> (car (cdr (cdr a)))
3

This is not the only way to create a list though. You can also use the list function, as well as the quote form to form a list as well:

scm> (list 1 2 3)
(1 2 3)
scm> '(1 2 3)
(1 2 3)
scm> '(1 . (2 . (3)))
(1 2 3)

There are a few other built-in functions in Scheme that are used for lists:

scm> (define a '(1 2 3 4))
______
a
scm> (define b '(4 5 6))
______
b
scm> (define empty '())
______
empty
scm> (append '(1 2 3) '(4 5 6))
______
(1 2 3 4 5 6)
scm> (length '(1 2 3 4 5))
______
5
scm> (null? '(1 2 3)) ; Checks whether list is empty.
______
False
scm> (null? '())
______
True
scm> (null? nil)
______
True

Question 6

Create the structure as defined in the picture below.

rlist

(define structure
'YOUR-CODE-HERE
(cons (cons 1 '()) (cons 2 (cons (cons 3 4) (cons 5 '()))))
)

Question 7: Remove

Implement a function (remove item lst) that takes in a list and returns a new list with all instances of item removed from lst.

(define (remove item lst)
'YOUR-CODE-HERE
(cond ((null? lst) '()) ((equal? item (car lst)) (remove item (cdr lst))) (else (cons (car lst) (remove item (cdr lst)))))
)

Extra Questions

The following questions are for extra practice — they can be found in the lab09-extra.scm file. It is recommended that you complete these problems on your own time.

Question 8: Composed

Write the procedure composed, which takes in procedures f and g and outputs a new procedure. This new procedure takes in a number x and outputs the result of calling f on g of x.

(define (composed f g)
'YOUR-CODE-HERE
(lambda (x) (f (g x)))
)

Question 9: Greatest common divisor

Let us revisit a familiar problem: finding the greatest common divisor of two numbers. Let's try writing this problem recursively in Scheme!

Write the procedure gcd, which computes the gcd of numbers a and b. Recall that the greatest common divisor of two positive integers a and b is the largest integer which evenly divides both numbers (with no remainder). Euclid's algorithm states that the greatest common divisor is

In other words, if a is greater than b and a is not divisible by b, then

gcd(a, b) == gcd(b, a % b)

You may find the provided functions min and max helpful. You can also use the built-in modulo function.

(define (max a b) (if (> a b) a b))
(define (min a b) (if (> a b) b a))
(define (gcd a b)
'YOUR-CODE-HERE
(if (not (or (= a 0) (= b 0))) (if (= (modulo (max a b) (min a b)) 0) (min a b) (gcd (min a b) (modulo (max a b) (min a b)))) (max a b))
)

Question 10: Filter

Create a function (filter f lst), which takes a predicate function f and a list lst, and returns a new list containing only elements of the list that satisfy the predicate.

(define (filter f lst)
'YOUR-CODE-HERE
(cond ((null? lst) '()) ((f (car lst)) (cons (car lst) (filter f (cdr lst)))) (else (filter f (cdr lst))))
)

Question 11: all-satisfies

Implement a function (all-satisfies lst pred) that returns True if all of the elements in lst satisfy pred.

(define (all-satisfies lst pred)
'YOUR-CODE-HERE
(= (length (filter pred lst)) (length lst))
)

Question 12: Accumulate

Fill in the definition for the procedure accumulate, which takes the following parameters:

  1. combiner: a function of two arguments
  2. start: a number with which to start combining
  3. n: the number of terms to combine
  4. term: a function of one argument that computes the nth term of the sequence

accumulate should return the result of combining the first n terms of the sequence.

(define (accumulate combiner start n term)
  (if (= n 0)
      start
'YOUR-CODE-HERE
(combiner (accumulate combiner start (- n 1) term) (term n))
))