Try Firefox!

SI413 Scheme Lab 1: Scheme Basics


Firing up Dr. Scheme
At the command prompt simply type "drscheme &" and you should see the interpreter/code-entry split window open up. [Note: You have to choose a "language" before you start doing anything. Under the menu: Lanugagues → Choose Language choose PLT → Pretty Big.] The lower pane is the interpreter. You can enter a scheme expression like (+ 3 4) at the prompt and the return key will cause the interpreter to evaluate the expression - provided the cursor is at the end of the line. You can get the cursor to the end of the line instantly with a ctrl-e combination (control and e keys pressed simultaneously).

Arithmetic Expressions
An expression in Scheme is either an atomic object (a number, a symbol, a string) or it is a list of expressions inside parenthesese - elements of which are space separated. When a list is evaluated by the interpreter, the first element is treated as a function, and the other elements are treated as arguments to the function. So, instead of sqrt(2.4), in Scheme we say (sqrt 2.4). Of course, expressions can be nested (composition of functions), so sqrt(3.4*2.9) is expressed in Scheme as (sqrt (* 3.4 2.9)).

Numeric Types
Section 6.2 of the R5 Scheme language definition describes numbers in Scheme. Here are some high points. Integers in Scheme can be arbitrarily large (BigInts, you'll hear people say), and a number written without a decimal point is given type integer. Floating-point numbers (type "real") are essentially doubles, and if you write a number with a decimal point it'll be treated as a real.. Scheme has built it rational numbers (fractions) and complex numbers as well. We'll concentrate on integers and reals.

Integer functions
All the below functions return integer values.

+            -             *
quotient     remainder     modulo
max          min           abs
numerator    denominator   gcd
lcm          floor         ceiling
truncate     round         rationalize
expt
Notice that division isn't there. Division returns a rational value.

Floating -point functions The above functions that make sense for reals are defined for reals, additionally you have others like:

sin  cos   tan
sqrt 
which ought to be self explanatory.

Exercises:

  1. Write 4.7*(34.453 - 47.728) + 3.7 as a scheme expression, and find its value.
  2. Functions like +,-*,max, and min that make sense for more than two arguments (fewer than two as well!) work just fine for more than two arguments. For example: (+ 1 2 3) evaluates to 6 just like you'd expect. Write an expression that evaluates to the largest value of sqrt(5), sin(1) + sin(2) + sin(3), and 17/8. Note: Remember that integer division produces rational numbers. The function exact->inexact converts an exact value (integer or rational) into an inexact (floating-point). Of course if you're clever, you won't need it ...
  3. Write a scheme expression that evaluates 2*x^3 - x^2 + 3*x - 5 at x = 2.451.

Functions
You may have noticed that the last of the previous problem was a bit of a pain, because you had to write 2.451 over and over. Just be glad it wasn't 34.98789798723! A nicer thing to do would be to create a function for 2*x^3 - x^2 + 3*x - 5 and then just call the function with argument 2.451. So how do you write a function? Well, the above would be:
> (define (f x) (+ (* 2 x x x) (* -1 x x) (* 3 x) -5))
> (f 2.451)
25.793878702
This is actually a bit of a shortcut for a function definition, as we'll discuss later. Clearly, you're defineing a new function, whose name is f and whose parameter names are everything else following in the parentheses (just x in this case), and then what follows is an expression that is the value of the function. For a simpler example, a function called my-ave might be defined like this:
(define (my-ave x y) (/ (+ x y) 2.0))
	
Hopefully the three components are clear.

Exercises

  1. Write functions to-celsius and to-fahrenheit that convert back and forth from Celsius to Fahrenheit. Recall: T_C = 5/9(T_F - 32)
  2. Write a function my-dist that takes two arguments, x and y, and computes the distance of point (x,y) from the origin.
  3. Define a scheme function called test-trig for sin(x)^2 + x*cos(x).

Control: if's and cond's
In C++ if's are statements. That means that they do not have type and they do not have value. Instead they describe a process whose side effects (output, changes in variable values, etc) embody the actual computation. In Scheme everything is an expression, and that includes if's. An if expression has three parts: the test condition, the "then" expression and the "else" expression. The value of the if expression is either the "then" expression (when the test evaluates to true) or the "else" expression. For instance, the following expression evalutes to the absolute value of x:
( if (< x 0) (* -1 x) x )
You see, depending on the result of the test, we either get the value -1*x or simply x itself. We can, of course, use this to get define an absolute value function:
(define (my-abs x) (if (< x 0) (* -1 x) x))
	
although, of course, scheme already has one. We can nest these expressions as needed.

The logical operators and, or and not are present in scheme just like you'd suppose. True in scheme is the constant #t and false is the constant #f. For example:

> (and (> 1 0) (< 2 -3))
#f
> (or (> 1 0) (< 2 -3))
#t
Numbers can be compared with the usual: i.e <, >, ≤, ≥, =. Note: for non-numeric objects the equal? is what you want to use (also works on numbers).

Exercises:

  1. Define a function called signed-inc, which returns 1 plus its argument if the argument is non-negative, and -1 plus its argument if the argument is negative.
  2. Redefine signed-inc so that it returns its argument unchanged if the argument is 0 but otherwise acts as before.
  3. Define a function middle that takes three numbers as argument and returns the middle of the three. E.g. (middle 4 2 3) should evaluate to 3.

There's also a nice function cond that's useful when you have a bunch of distinct cases to consider. For example, if you want to define a function whats-your-sign that returns 1,0,-1 according to the sign of a number, you miught use cond as follows:

(define (whats-your-sign x)
        (cond ((< x 0) -1)
              ((> x 0) 1)
              ((= x 0) 0)
        )
)
	
So cond's arguments form a list of condition/result pairs. The interpreter finds the first condition that is satisfied, and the value of the cond expression is the result associated with that condition.

Exercises:

  1. Rename your middle function to middle-old. Then define middle from using cond's and and's.

Recursion!
To do anything interesting in Scheme we need recursion. The reason is that we don't have loops! Suppose, for example, we wanted to define a function sum-range that would sum all the integers in the given range. Normally we'd think of using loops for this, but Scheme doesn't do loops! They're contrary to the idea of referential transparency - i.e. no side effects. Loops are all about creating side effects - over and over and over. At any rate, we need to use recursion:
(define (sum-range i j) (if (= i j) i (+ i (sum-range (+ i 1) j))))
Recursion is nothing new for you folks, you'll just use a lot more of it in Scheme.

Exercises:

  1. Define a factorial function. Try taking the factorial of 111. What do you notice about the result. Do you think the straightfoward C++ implementation could've given you this result?
  2. I want to compute compound interest. Write a function accrue that takes a balance, an annual interest rate, and a number of years and returns the balance at the end of that time period asuming that interest is computed monthly. Recall: If the annual interest rate is r and the balance is B, then one month's compounding produces a new balance of B*(1 + r/1200) (we're assuming a value of, say, 7.5 for r means 7.5%). Hint: A nice bottom-up solution to this would probably involve writing a (compound-month B r) function, that just does one month of compounding, testing it, and then moving on. Next, write an accrue-months that would take a balance, a rate, and a number of months, and compute the balance at the end of that many moenths - test it! Then it should be easy to write your accrue function.
  3. Write a function fib to compute Fibonocci numbers. Recall, fib(0) and fib(1) are both 1. All the rest are defined by the rule fib(n) = fib(n-1) + fib(n-2).

let
The let construct in Scheme allows you to give a name to a common subexpression. For example, consider the expression
(33 * (501 - 33)) / ( 1 - (33 * (501 - 33)))
	
The natural way to code this in Scheme is probably
(/ (- (* 33 501) 33) (- 1 (- (* 33 501) 33)))
	
But you could say "let a = 33 * (501 - 33), and return a / (1 - a)". That's essentially what let allows you to do.

(let ((a (- (* 32 501) 33))) (/ a (- 1 a)))
	
Essentially, let is the Scheme way of getting local variable functionality. What you've got is the let keyword, followed by a list of variable-name/value pairs, followed by an expression (presumably using the new names) that provides the value of the whole let expression. For example:
> (  let  ((a 2) (b 4) (c 6))  (+ a b c) )
12
	
Using let in functions
There is power in using let in functions. Suppose I want to define a function called shifted-cube, which computes (x + 1)^3 for a value x.
> (define (shifted-cube x) (let ((a (+ x 1))) (* a a a)))
> (shifted-cube 2)
27
	

Exercises:

  1. Using what you just learned, write a function called test-sin that computes 1/(sin(x)^2 + 1) + sqrt(sin(x)^2 + 1) - (sin(x)^2 + 1)^2
  2. Write a function dist that computes the difference (in inches) between two lengths (given in feet and inches). Example:
    > (diff 3 7 2 11) ← difference between 3' 7'' and 2' 11''
    8
    
    Write this function using a let expression to create values L1 and L2 for the lengths in inches of the input feet-and-inches lengths.


Christopher W Brown
Last modified: Wed Aug 27 09:02:58 EDT 2008