Scheme Introductory Lab 2

Table of Contents

This assignment is to be done individually. You can talk to other people in the class, me (Dave), and any of the course staff (graders, lab assistants, teaching assistants, prefects) for ideas and to gain assistance. You can help each other debug programs, if you wish. The code that you write should be your own, however, and you shouldn't directly share your code with others. See the course syllabus for more details or just ask me if I can clarify.

Start working in the same repl within Repl.it that you used for the previous lab. If you're having trouble finding it, click on the "hamburger" menu on the top-left of the Repl.it screen, and choose "My Repls"; you should hopefully see it there. Most of the tasks here are simply for your learning, not to be turned in, but you should do them and ask online any questions that you have. There are tasks at the end to be submitted, with instructions on how to do so.

1 Get set up

First, let's make sure you're in the right directory in the terminal window. If you have just started up Repl.it from scratch, you may need to make sure to move into your scheme-lab-username directory (where username should be your actual username), since by default you start in your home directory. Here's how you can check: in the terminal, type in

pwd

If what you see looks something like /home/runner/your-repl-name but does not contain the name of your Git repository (i.e. scheme-lab-username), type the following (where username is your actual username)

cd scheme-lab-username

Then type pwd again, and you should see something like

/home/runner/your-repl-name/scheme-lab-username

Then we're ready to go.

2 Run a saved program

Entering your code interactively is fun, but not a good idea for creating large programs. A better way to go is to write your code in a file, saving it, and then loading it directly. Here's how to do it. In principle, this could be a file with any name, but I've created one called main.scm in order for you to get started. So find the file main.scm in Repl.it on the left side, open it, and enter in the following one line of Scheme code:

(+ 1 2)

You can then start up Scheme as usual by typing the following in the terminal window:

./scheme

Finally, load in the code you wrote with the following Scheme code:

scheme@(guile-user)> (load "main.scm")

If this worked correctly, you should see the results of running your code.

3 Conditionals

3.1 if

Enter the following code into main.scm:

(if (equal? 8 3)
    9
    10)

Run the code, then modify the condition following if to get a different value to return. Questions for you to try to answer yourself:

  • What does if do?
  • Where is the traditional then and else that you would expect to see?

3.2 cond

Now enter the following code into main.scm, and run it:

(cond ((equal? 16 3) (+ 3 8))
      ((equal? 16 8) 12)
      (else (* 6 3)))

See if you can you replace all of the 16's in the above code with some other value to change the output value? Questions for you to try to answer yourself:

  • What does cond do?
  • What are the varying levels of nested parentheses for?

3.3 Equivalence tests

Try this code (you might just copy and paste all of it):

(equal? '(hi there) '(hi there))
(equal? '(hi there) '(bye now))
(equal? 3 3)
(equal? 3 (+ 2 1))
(equal? 3 3.0)
(equal? 3 (/ 6 2))
(equal? -1/2 -0.5)
(eqv? '(hi there) '(hi there))
(eqv? '(hi there) '(bye now))
(eqv? 3 3)
(eqv? 3 (+ 2 1))
(eqv? 3 3.0)
(eqv? 3 (/ 6 2))
(eqv? -1/2 -0.5)
(= 3 3)
(= 3 (+ 2 1))
(= 3 3.0)
(= 3 (/ 6 2))
(= -1/2 -0.5)
(= '(hi there) '(hi there))  ;; yes, this will give an error

See what kind of responses you get. Try to think through for yourself how equal?, eqv? and = are different. Ask questions online as appropriate.

4 Defining functions

4.1 lambda

In main.scm, enter this code, and then load it:

(lambda (x)
  (+ x 1))

You should observe that the lambda function returns a function without a name. In this case, you have defined a function that takes a parameter and adds 1 to it. Functions in Scheme are called procedures by Guile. I'll generally use either term depending on context.

4.2 Give it a name

A function without a name is useless, as it is immediately garbage collected. Try this instead:

(define add-one
  (lambda (x)
    (+ x 1)))

Save this code, load it, then type (add-one 5) at the interactive prompt. You should see the results. So what happened here? define created a pointer named add-one, which points to the function you just created.

4.3 Aliasing

Add the following two lines to main.scm, then load it at the interactive prompt.

(define another-add-one add-one)
(another-add-one 5)

Try to explain to yourself what the above code does.

4.4 Local variables

You can declare local variables in Scheme via the use of let. For example, try the following code:

(define a 5)
(define b 6)
(define c 7)
(define strange
  (lambda (x)
    (let ((a 1) (b 2) (c 3))
      (+ x a b c))))

After executing this code, see what are the values of a, b, and c are. Then see what what you get when you make the call (strange 3)? Try to explain to yourself what's going on.

4.5 Recursion

Here's a factorial, done recursively. Copy and paste, run it, and see if you can make sense out of what it is doing.

(define fact
  (lambda (n)
    (if (equal? n 1)
        1
        (* n (fact (- n 1))))))

Note that there is no return statement here as you would find in other languages you may know. In Scheme, return is implicit and automatic.

One super useful technique for debugging is to use Guile's tracing ability. It's a little finicky, but it's wonderfully helpful. To do this, copy and paste the entire fact function defined in main.scm, then use the Guile command ,tr (that's a comma at the beginning of the line) with a function call to trace it. I've copied and pasted my complete session here so you can see what it looks like.

scheme@(guile-user)> (define fact
  (lambda (n)
    (if (equal? n 1)
        1
        (* n (fact (- n 1))))))
scheme@(guile-user)> ,tr (fact 5)
trace: (fact 5)
trace: |  (fact 4)
trace: |  |  (fact 3)
trace: |  |  |  (fact 2)
trace: |  |  |  |  (fact 1)
trace: |  |  |  |  1
trace: |  |  |  2
trace: |  |  6
trace: |  24
trace: 120

Tracing can help you see all of your recursive calls, and what the parameters and return values are at each shot.

Try this out yourself to make sure you can make it work.

Unfortunately, tracing doesn't work if you load the function first; it only works if you enter/paste it directly at the interactive prompt. That's cumbersome. But there is a workaround you can use: you can redirect input to the interactive prompt from a file. Here's how to do so. First, create another file in Repl.it; let's call it factorial.scm. Then paste the above fact function definition into that file, followed by the trace command. Here's how my factorial.scm looks:

(define fact
  (lambda (n)
    (if (equal? n 1)
        1
        (* n (fact (- n 1))))))

,tr (fact 5)

Don't miss the fact that I've got at least one newline at the end of the file: that's important.

Then, in the terminal window, quit Guile. You can do so by typing ,q. Once you are back at the bash prompt (shown with >), enter the following command:

./scheme < factorial.scm

Don't miss the < above before factorial.scm. This is the bash redirect operator. It means that ./scheme should run as if you were using it interactively, but it should simulate you typing in the input from factorial.scm. That's why the newline at the end of the file is important; that's necessary to simulate you typing Enter after the last command. If this works correctly (try it!) you should see output like:

> ./scheme < factorial.scm
GNU Guile 2.2.3
Copyright (C) 1995-2017 Free Software Foundation, Inc.

Guile comes with ABSOLUTELY NO WARRANTY; for details type `,show w'.
This program is free software, and you are welcome to redistribute it
under certain conditions; type `,show c' for details.

Enter `,help' for help.
trace: (fact 5)
trace: |  (fact 4)
trace: |  |  (fact 3)
trace: |  |  |  (fact 2)
trace: |  |  |  |  (fact 1)
trace: |  |  |  |  1
trace: |  |  |  2
trace: |  |  6
trace: |  24
trace: 120

Again, this is a really helpful way to help you debug your code.

5 Your tasks

5.1 sum

In the main.scm file that I provided, write a recursive function named sum that adds up all the elements in a list. For example:

(sum '(4 5 0 1))

should return

10

Special cases:

  • If the list is empty, it should return 0.
  • We will not test your code with anything other than a (possibly empty) simple list of numbers.

5.2 keep-first-n

Also in the main.scm file that I provided, write a recursive function named keep-first-n, that returns a list of the first n elements in a list.

Example:

(keep-first-n 3 '(a b c d e f g h i))

should return

(a b c)

Special cases:

  • If n is 0, you should return the empty list.
  • If n is bigger than the length of the list, or if n is negative, I don't really care what it returns so long as it isn't an error message or complete gibberish. Returning just the list itself, the empty list, or #f are all reasonable options.

Your function should not use any other Scheme functions than those which have been introduced in this lab. [An exception: if you wish, you may use the functions <, >, <=, or >=.]

5.3 How to test your work

You're going to submit your work back to GitHub, instead of doing so on Moodle. One of the great things about doing this is that I have some tests set up that will automatically run. If the tests pass, your code is correct!

To submit your work back to GitHub, first issue this command in the terminal window:

git status

The first command should show that your main.scm file has changes ready to be committed. So to commit these changes, enter this command:

git commit -am "I think my main.scm is ready."

This command will commit the changes to your local cloned Git repository. However, since/if this is your first time doing a commit in this repository, you will likely receive an warning that your name and email address were configured automatically. To make that message go away in the future, copy and paste into the prompt the two git config messages that it will show you, making sure to use your Carleton email address for the email. Then try to commit again:

git commit -am "I think my main.scm is ready."

Once you have successfully added your file and committed it to the local repository, push it to GitHub by issuing the following command:

git push

Hopefully the above works as expected. If so, go to github.com in your browser, and find the repository there that you're working on. You should be able to see all of the files, with your most recent updates. Perhaps most importantly, in the blue bar above your files, you should hopefully see a yellow dot, a green checkmark, or a red X next to a label that says "Latest commit." I have your repository set up to automatically run the tests inside of the file tests.scm on your code. A yellow dot means the tests are still running; a green checkmark means they passed; and a red X means at least one of the tests failed for some reason. If you click that icon, and then click "Details" in the box that pops up, you'll be taken to a window where you can see the results of your tests. Open up the "Run Tests" section in the black window, and it will show you which tests failed, and what their line numbers are in the tests.scm file.

If you did have some tests not pass, you can go back and try again; then repeat the git commands above, starting with git status. DO NOT CHANGE tests.scm.

If the tests all pass, you are likely to receive full credit for the assignment! We will be looking at your code, however, and will deduct points if your code is designed to only work on these specific tests without generalizing.

5.4 How to submit by tagging your final version

Whenever you push your code, that sends another version (called a "commit") to GitHub that we can see. Because you can keep submitting new versions, we need to know when you are done so that we know which commit to grade. To this, you will tag the commit you wish us to grade. Here's how to do so from the terminal prompt.

git commit -am "All my changes are committed, I may have already done this"
git push
git tag submitted
git push --tags

The above code does the following five things:

  1. Make a local commit consisting of your most recent changes. (If you haven't changed anything since the last time you committed, you'll get a message telling you there was nothing new to commit.)
  2. Push your changes to GitHub. (If you don't have any new commits since the last time you pushed, you'll get a message telling you everything is up to date.)
  3. Tag your most recent commit with the label submitted.
  4. Push your tags to GitHub. Strangely, a regular git push only pushes commits, and not tags.

Then look on GitHub after the last push, and click where it says "X commits" (where X is a number). That will show you the commit history. If you click on your most recent commit, you should be able to see the tag listed there.

You should wait to tag until you're sure you have committed the version you want us to grade. That said, in the unlikely event that you goof and realize that you want to commit a newer version for us to grade, you'll need to use another tag. Reusing the same tag for a different commit is generally a really bad idea. If you need to tag another commit, create another tag called submitted.1 (or whatever version number you're up to). Make sure you first add, commit, and push your commit before tagging.