SI 413 Fall 2023 / Admin


This is the archived website of SI 413 from the Fall 2023 semester. Feel free to browse around; you may also find more recent offerings at my teaching page.

Tips and tricks

1 Exiting things from the command line

There are many ways to kill or exit a process, but some are much cleaner than others. If you go for the more violent way every time, it can leave a big mess behind that you have to clean up later.

"Not leaving a mess" is especially important if your program is using a bunch of memory/CPU, or if it's editing a file that you care about. So follow these quick instructions to keep things tidy, and save the gory methods for when they're really needed.

1.1 Clean exits from python, scheme, etc

For most interactive command-line interpreters such as Python, ChezScheme, and even the SPL interpreter you will write, the "clean" way to exit is by entering Ctrl-D. This tells the program, essentially, "I'm done typing here", and will generally result in a nice, clean exit

1.2 man and less

These programs are showing you a text file (or something like it), or allowing you to edit a file right in the terminal.

For less and man, the standard way to quit is by simply typing q.

1.3 vi and vim

For vi or vim, you also quit with the "q" command, but it's a little more complicated. If you are in "insert mode" to actually make edits, then you have to hit escape first. And then you have to type a colon to get to the command prompt. And then if there are unsaved changes, you have to tell vim how to deal with that.

Here's a summary of how to get out of vim. Make sure you actually type the colon after hitting escape!


Key sequenceWhat it does
<Esc>:qQuit if nothing in the file was changed
<Esc>:wqSave your changes, then quit
<Esc>:q!Quit right now, changes be damned

1.4 Violence

Sometimes, being a nice person just isn't enough to get your way. Here are some more violent ways to end a command-line program, in order of increasing levels of destruction.

  1. <Ctrl>-C: Sends SIGINT, asking nicely for the process to end itself.
  2. <Ctrl>-Z: Sends SIGSTOP, asking the shell to stop (pause) the process and bring you back to the shell. At this stage the program is not dead! You still have to kill it!
    The shell will show you a line like:
    [1]+  Stopped                 ./myprogram
    The [1] tells you the job number, which might be larger than 1 if you have other stopped jobs in the same terminal. You use %1 to refer to this process for the kill command.
    Try killing with this order of urgency:
    • Gentle request (SIGINT): kill -2 %1
    • More urgent (SIGTERM): kill -15 %1
    • No more Mr. Nice Guy (SIGKILL): kill -9 %1

2 Debugging with ChezScheme

There is a nice article here about the many ways to debug a program in ChezScheme. My favorite part is the technique of "Staring at the code". Below are two of the more concrete tools that you might find useful.

I am going to use the following (faulty) Scheme program as an example. It is supposed to take any list and return a new list with every other element from the first one.

(define (every-other L)
  (if (null? (cdr L))
    L
    (cons (car L)
          (every-other (cdr (cdr L))))))
When we run this, it works sometimes but not other times:
> (every-other '(1 2 3 4 5))
(1 3 5)
> (every-other '(1 2 3 4 5 6))
Exception in cdr: () is not a pair
Type (debug) to enter the debugger.

Let's look at two ways we can use ChezScheme to help us figure out what went wrong here.

2.1 trace

The first useful tool to debugging a recursive function is to trace it. What that means is that, every time the function gets called (recursively), the interpreter will show you what the arguments are, as well as the eventual return value. Very useful!

To trace a function, you just run (trace function-name) in Scheme, where of course you want to replace function-name with the name of your function.

Here you can see the same two function calls above, but with tracing enabled:

> (trace every-other)
(every-other)
> (every-other '(1 2 3 4 5))
|(every-other (1 2 3 4 5))
| (every-other (3 4 5))
| |(every-other (5))
| |(5)
| (3 5)
|(1 3 5)
(1 3 5)
> (every-other '(1 2 3 4 5 6))
|(every-other (1 2 3 4 5 6))
| (every-other (3 4 5 6))
| |(every-other (5 6))
| | (every-other ())
Exception in cdr: () is not a pair
Type (debug) to enter the debugger.

2.2 debug

The other option you have is to actually use ChezScheme's built-in debugger. Start by typing (debug) when it asks you:

> (every-other '(1 2 3 4 5 6))
Exception in cdr: () is not a pair
Type (debug) to enter the debugger.
> (debug)
debug>

At any of the debug-related prompts, you always have two commands you can type:

  • ?: show a help screen with available commands
  • q: quit out of the debugger and go back to the Scheme interpreter

If you just want to find out where in the code your error came from, you can start with the i command to inspect the error, and then type show to see the expression that caused the error, or file to get the line and column number in your .scm file where the issue came from:

> (every-other '(1 2 3 4 5 6))
Exception in cdr: () is not a pair
Type (debug) to enter the debugger.
> (debug)
debug> i
#                                    : file
line 5, character 11 of error_example.scm
#                                    : s
  continuation:          #
  procedure code:        (lambda (L) (if (null? (...)) L ...))
  call code:             (every-other (cdr (cdr L)))
  frame and free variables:
  0: 5

This is telling us that the error occurred on line 5, character 11 of my file. And the specific expression is the recursive call on (every-other (cdr (cdr L))).

(To fix this program, we need another base case for when the list is null.)