Skip to content

Everything you need to know about Scheme

Jakub T. Jankiewicz edited this page Jan 4, 2024 · 11 revisions

Lisp Cycles xkcd comic

Lists

The base of any Lisp language are lists, this is a basic math expression:

(+ 1 2 (* 3 5 (/ 1 2)))

This notation is used because everything in Lisp is a function or a macro.

So in other languages, it would need to be:

+(1, 2, *(3, 5, /(1, 2)))

that looks kind of weird, in Scheme and LIPS + * and / are functions.

if you call just the function or try to print it you will see it:

(display +)
;; ==> #<procedure>

In LIPS REPL (read eval print loop - live interpreter) you can call help to see the docs for that function.

(help +)
(+ . numbers)

Sum all numbers passed as arguments. If a single value is passed it will
return that value.

Getting back to lists, they are created from cons pairs:

(cons 1 2)
;; => (1 . 2)

This will create a pair. With pairs, you can create any binary tree.

Everything after ; is a comment. LIPS that implement R7RS specification also support #; syntax that is an inline comment, the commented-out code is a single expression after the #; sequence of characters.

;; this is a comment
(list 1 2 #;(this is also a comment) 3)
;; => (1 2 3)

To access the elements of the pair you use:

(car (cons 1 2))
;; ==> 1
(cdr (cons 1 2))
;; ==> 2

You create a list of cons cells like this:

(cons 1 (cons 2 (cons 3 (cons 4 '()))))
;; ==> (1 . (2 . (3 . (4 . ()))))

this is abbreviated as:

(1 2 3 4)

The same can be created using:

'(1 2 3 4)
;; or
(list 1 2 3 4)

The first is quoting second is a list function. What's different can be seen in this example:

'(1 (+ 2 3) 4)
;; ==> (1 (+ 2 3) 4)
(list 1 (+ 2 3) 4)
;; ==> (1 5 4)

Quote creates a literal list while the list will evaluate its arguments

Variables definition

You can create global and local variables:

(let ((x 10) (y 20))
  (+ x y))

let is a special construct called a macro that creates new local variables x and y and evaluates the expression.

To create a global variable you use define:

(define x 10)
(display x)
(newline)

This will define the variable x and print it.

function definition

(define (square x) (* x x))
;; same as
(define square (lambda (x) (* x x))) ; variable and anonymous function

Numbers

Scheme and LIPS define 4 number data types:

  • Integers (in LIPS they are big ints).
  • Rational (fractions).
  • Real (created using JavaScript basic numbers that are floats).
  • Complex number (extension of real numbers with an imaginary part that (sqrt -1))
(list 100 1/100 10+10i 0.1)
;; ==> (100 1/100 10+10i 0.1)

This is basic syntax real numbers can use scientific notation same as big ints:

(list 10e2 10e-2)
;; ==> (1000 0.1)

You can define fractions by calling / function.

(/ 1 2)
;; ==> 1/2

to get a real number you need to call:

(exact->inexact (/ 1 2))

Complex numbers can have mixed real and imaginary parts (at least in LIPS):

(list 10+0.5i 1/2+0.5i 0.5+1/2i)
;; ==> (10+0.5i 1/2+0.5i 0.5+1/2i)

They will be coerced when doing operations on them.

Conditionals

(let ((x 10))
  (if (< x 100)
      (display "true")
      (display "false")))

With if you can have only one expression when the value is true or false. If you want more expressions you can use begin:

(let ((x 10))
  (if (< x 100)
      (begin
         (display "true")
         (newline))))

Loops

In the latest specification there is a do macro that can create loops:

(do ((i 1 (+ i 1))
     (result '() (cons i result)))
    ((> i 10) result)
  (display i)
  (newline))

Macro do creates new variables like let but 3rd element if the variable list is increment expression. So the first variable is i which is incremented using (+ i 1) second is list construction using cons. The second value in the do macro is the test and result of the do expression. So what this will do is to print all numbers from 1 to 10 and return the list in reverse order. To get a list in order you can use (reverse result).

In the old spec of Scheme, the only way to create loops was to do recursion (one feature of Scheme is that according to spec it needs to optimize so-called tail calls that allow to creation of infinite loops that don't consume the stack and overflow, LIPS don't have TCO - tail call optimization).

Below are a few examples of creating loops using a recursive process:

(letrec ((loop (lambda (x)
                   (if (> x 1)
                       (* x (loop (- x 1)))
                       1))))
    (loop 10))

;; ==> 3628800
(let loop ((x 10))
  (if (> x 1)
      (* x (loop (- x 1)))
      1))
;; ==> 3628800

(define (! x)
  (if (> x 1)
      (* x (! (- x 1)))
      1))
(! 10)
;; ==> 3628800

To make the Scheme optimize the recursive expressions you need to use a recursive call as the last value.

(define (! x)
  (let loop ((x x) (result 1))
    (if (> x 1)
        (loop (- x 1) (* x result))
        result)))

(! 100)
;; ==> 93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000

It works in this way, when using normal recursion if call the inner function (same function) and you wait until the function finishes but that function calls another function, you keep waiting until the last function is executed and the function calls are using a stack to save the state. By using a variable as our state (result variable) we no longer need the stack and wait and the last function call doesn't need to return it can get back directly at the starting point. This is done by the Scheme system automatically.

With this function if Scheme implements TCO you can even execute (! 10000) (it doesn't work in LIPS yet).

Lisp macros

One of the macros you can use in some Scheme implementations (including LIPS) are Lisp macros:

(define-macro (when test . body)
   `(if ,test
        (begin
           ,@body)))

(let ((x 10))
  (when (< x 100)
     (display "x")
     (newline)))

Macros allow to creation of new constructs, here a new when special kind of operator that would be impossible to create using functions.

Symbol ` is a special kind of quote/escape similar to ', but inside you can unescape using , or ,@. First, just inject the value into the quoted list, and second remove the surrounding parenthesis.

Hygienic macros

One problem with like with created as a Lisp macro, is that inside the macro there are if and begin names, that are used directly and it will break when the user defines variables named like that, which makes happen more often then you think in Scheme.

(define-macro (unless test . body)
   `(if (not ,test)
        (begin
           ,@body)))

(let ((x 10) (not (lambda (x) x)))
  (unless #t
     (display "x")
     (newline)))

In Scheme, they solved this problem by using hygienic macros. Base hygienic macros are those created by syntax-rules that use pattern language:

(define-syntax unless
   (syntax-rules ()
      ((_ test body ...)
       (if (not test)
           (begin
              body ...)))))

(let ((x 10) (not (lambda (x) x)))
  (unless #t
     (display "nope")
     (newline)))

(let ((x 10) (not (lambda (x) x)))
  (unless #f
     (display "sure")
     (newline)))

Ellipsis is a special symbol that is used with a symbol before it. body ... should be considered a single symbol that will match an empty list of any number of items. In the above example, it will match the list with two elements ((display "sure") (newline)). that will be inserted into the body, similar to ,@ in lisp macros.

Streams

See streams.scm

Continuations

Not yet implemented in LIPS.

Scheme Language Specification

Scheme language is defined in RnRS specifications, latest is R7RS. There are also Scheme extensions (SRFI - Scheme Request For Implemtation), that can be used with Scheme. Some Scheme implementations added those as libraries that you can use in the code.