SketchyLISP Reference Manual - Copyright (C) 2005 Nils M Holm

5 Primitive Functions

5.1 Introduction

Primitive functions are those functions that practically cannot be expressed in terms of SketchyLISP. These functions form the core of the language.

In this chapter '(X.Y) denotes a generic Pair and '(X Y) denotes a generic List. A Symbol is called a Symbol, if it is bound to itself (or quoted) and otherwise it is called a Variable. Symbols may be referred to as Variables, but not vice versa.

'X denotes a Symbol and X denotes a Variable.

The Symbol bottom is used to denote an undefined result.

5.2 APPLY

(apply fun list) => eval[(fun.list)]

Apply applies the given function (fun) to the arguments contained in the given list. Both fun and list are passed to apply call-by-value, but fun is applied to the arguments in list call-by-name. The number of arguments of fun must match the number of members of list. For example,

(apply cons '(a b))       => '(a.b)
(apply cons (list 'a 'b)) => '(a.b)
(apply cons '(a))         => bottom
(apply car (list '(a.b))) => 'a
(apply list '(cons a b))  => '(cons a b)

The apply function conforms to R5RS.

5.3 BOTTOM

(bottom ...) => bottom

(Bottom) evaluates to an undefined result. Any expression that contains a subexpression evaluating to (bottom) evaluates itself to (bottom). The bottom function may have any number of arguments.

(bottom)           => bottom
(bottom 'x 'y 'z)  => bottom
(eq? (bottom) ())  => bottom

The bottom function is specific to SketchyLISP.

5.4 CALL/CC

(call/cc f) => #<continuation>

(Call/cc f) captures the 'current continuation' and passes it to f. F must be a function of one argument:

(call/cc (lambda (k) k)) => ((lambda (k) k) #<continuation>)

The current continuation (represented by #<continuation>) is the part of a formula that will be evaluated next. For example, in

(cons 'foo (call/cc (lambda (x) 'bar)))

the (current) continuation of

(call/cc (lambda (x) 'bar))

is

(cons 'foo _)

where _ denotes the not-yet-evaluated application of call/cc.

Applications of call/cc reduce to the result of the function passed to call/cc unless this function applies its argument. For instance:

(call/cc (lambda (ignored) 'foo)) => 'foo

If the function passed to call/cc applies the captured continuation passed to it, though, the current context of the formula is discarded and replaced with the continuation. The application of call/cc in the restored context is replaced with the argument of the continuation:

(cons 'foo (call/cc (lambda (k) (k 'bar))))
=> (cons 'foo ((lambda (k) (k 'bar)) #<continuation>))
=> (cons 'foo (#<continuation> 'bar))
=> (cons 'foo 'bar)

and, more important:

(cons 'foo (call/cc (lambda (k) (cons 'zzz (k 'bar)))))
=> (cons 'foo ((lambda (k) (cons 'zzz (k 'bar))) #<continuation>))
=> (cons 'foo (cons 'zzz (#<continuation> 'bar)))
=> (cons 'foo 'bar)

Because activating the continuation k replaces the current context with the one previously captured by call/cc, the

(cons 'foo (cons 'zzz ...) ...)

part is thrown away and replaced with

(cons 'foo _) 

Finally, the application of call/cc is replaced with the value passed to k.

Because call/cc can be used to expose the order of evaluation of function arguments, the reduction of Lists, as explained in the previous section, is extended as follows:

Members of Lists are evaluated from the left to the right, so each Ai is guaranteed to be evaluated before Aj, if i<j in

(a1 ... aN)

Consequently,

(call/cc (lambda (k) (#f (k 'foo) (k 'bar)))) => 'foo

Once captured, SketchyLISP continuations have indefinite extent. As long as they can be referred to using a Symbol, they remain valid. Therefore they can be applied any number of times and even after returning from call/cc.

Since continuations cannot be expressed formally in terms of SketchyLISP, there is no formal description of call/cc.

The call/cc function conforms to R5RS.

5.5 CAR

(car '(x.y)) => 'x

(Car x) evaluates to the car part of x, which must be a Pair. The following rules apply:

(car '(x.y)) => 'x
(car '(x y)) => 'x
(car '(x))   => 'x
(car 'x)     => bottom
(car #t)     => bottom
(car 123)    => bottom
(car ())     => bottom

The car function conforms to R5RS.

5.6 CDR

(cdr '(x.y)) => 'y

(Cdr x) evaluates to the cdr part of x, which must be a Pair. The following rules apply:

(cdr '(x.y)) => 'y
(cdr '(x y)) => '(y)
(cdr '(x))   => '()
(cdr 'x)     => bottom
(cdr #t)     => bottom
(cdr 123)    => bottom
(cdr ())     => bottom

The cdr function conforms to R5RS.

5.7 COND

(cond (p1 e1) ... (pN eN)) => e#T

The arguments of cond are passed to it call-by-name. The function of cond is based upon the order of evaluation of its arguments. Each argument of cond must have the form

(Pj Ej)

where Pj must evaluate to one of the Symbols #t and #f. Ej may be of any type. Such an argument is also called a Clause.

Cond first evaluates P1. If it reduces to #t, cond evaluates E1. In this case, E1 is the normal form of the entire cond expression. Otherwise cond does not evaluate E1 and continues with the following arguments until a Clause with Pj=#t is found. The following rules apply.

(cond (x y) (x2 y2)) => eval[y], if eval[x]=#t
(cond (x y) (x2 y2)) => (cond (x2 y2)), if eval[x]=#f
(cond (x y) (x2 y2)) => bottom, if eval[x] not in {#t,#f}
(cond (x y))         => bottom, if eval[x]=#f

The cond pseudo function conforms to R5RS.

5.8 CONS

(cons 'x 'y) => '(x.y)

Cons creates a new Pair from two given expressions. Its first argument x forms the car part of the new Pair and its second argument y forms the cdr part. The following rules apply:

(cons 'x 'y)      => '(x.y)
(cons 'x ())      => '(x)
(cons 'x '(y.())) => '(x y)
(cons 'x '(y))    => '(x y)
(cons 'x '(y.z))  => '(x y.z)

Note: '(x y.z) is a called an Improper List or a Dotted List, because its last element is not equal to ():

(cdr (cdr '(x y.z))) =/= '()

The cons function conforms to R5RS.

5.9 DEFINE

(define x y) => 'x

Define binds the normal form of y to the Symbol x:

(define x y) => 'x ; x := eval[y]

Since its arguments are passed to define call-by-name, it does not matter what x is bound to when define is applied. The previous binding of x is lost.

Define is used in association with lambda to create new functions:

(define f (lambda (x) t))

Functions created using define may be mutually recursive.

Note: because define has a side effect (binding a new value to a Symbol), it should only be used for defining data structures and functions at the top level.

The define pseudo function conforms mostly to R5RS. It differs from R5RS in two points:

5.10 EQ?

(eq? x y) => {#t,#f}

(Eq x y) evaluates to #t, if X and y are identical, and otherwise to #f. Two expressions are identical, if, and only if they are both Symbols and have the same name or they are both the same Boolean literal. All other Atoms as well as Pairs may be different, even if they look equal. Objects bound to the same Symbol are always identical, so

(eq a a) => #t

even if a is a Variable. The following rules apply:

(eq? x x)           => #t
(eq? 'x 'x)         => #t
(eq? 'x 'y)         => #f
(eq? () ())         => #t
(eq? 'x '(x.y))     => #f
(eq? #f #f)         => #t
(eq? '(x.y) '(x.y)) => bottom
(eq? '(x y) '(x y)) => bottom
(eq? 123 123)       => bottom

The eq? function conforms to R5RS.

5.11 EVAL

(eval x) => eval[x]

Eval is the SketchyLISP interpreter itself. It reduces x to its normal form. Notice that

(eval (cons 'a 'b))

is in fact equal to

(eval '(a.b))

because eval is called by value. To pass

(cons 'a 'b)

to eval, it must be quoted:

(eval '(cons 'a 'b))

Eval has a side effect, if the expression evaluated by it does have a side effect. For instance:

x => ()
(eval '(define x 'y)) => 'x
x => 'y

The eval function is specific to R5RS. R5RS also provides an eval function, but it is not compatible to SketchyLISP's one.

5.12 EXPLODE

(explode 'c1c2...cN) => '(c1 c2 ... cN)

The explode function takes a single Symbol as argument. It explodes the given Symbol into a List of one-character Symbols. The order of characters of the original Symbol is preserved. The following rules apply:

(explode ())     => ()
(explode 'x)     => '(x)
(explode 'xyz)   => '(x y z)
(explode '(x))   => bottom
(explode '(x.y)) => bottom
(explode #t)     => bottom
(explode 123)    => bottom

The explode function is specific to SketchyLISP.

5.13 IMPLODE

(implode '(c1 c2 ... cN)) => 'c1c2...cN

The implode function takes a List of one-character Symbols and creates a new Symbol consisting of the characters contained in the List. The following rules apply:

(implode ())       => ()
(implode '(x))     => 'x
(implode '(x y z)) => 'xyz
(implode '(a bc))  => bottom
(implode 'x)       => bottom
(implode '(1))     => bottom
(implode '(#t))    => bottom

The implode function is specific to SketchyLISP.

5.14 LETREC*

(letrec* ((x1 y1) ... (xN yN)) z) => eval[z]

Letrec* binds each Symbol Xi to eval[Yi], thereby forming a local environment. The expression z is evaluated inside of that local environment. The normal form of z is the normal form of the entire letrec* expression.

Letrec* first creates the local Symbols X1...Xn and then evaluates Y1...Yn in the given order, binding each eval[Yi] to Xi as soon as Yi is evaluated. Subsequently, the environment of letrec* expands incrementally. Each Yj is evaluated in an environment where Xi is bound to Yi, if i<j, and so:

(letrec* ((bar 'baz)
          (foo  bar))
   foo)
=> 'baz

Letrec* may be used to create mutually recursive functions:

(letrec* ((even-p (lambda (x)
        (cond ((null? x) #t)
            (#t (odd-p (cdr x))))))
    (odd-p (lambda (x)
        (cond ((null? x) #f)
            (#t (even-p (cdr x)))))))
    (list (odd-p '(i i i i i))
        (even-p '(i i i i i))))
=> '(#t #f)

The letrec* function is specific to SketchyLISP, but is expected to appear in R6RS.

5.15 LAMBDA

(lambda (x1 ... xN) t) => #<closure (x1 ... xN)>

Applications of lambda reduce to lexical closures. If the term t of a lambda expression

(lambda (x) t)

contains any free variables, an association list (a list of key/value Pairs) is added to the resulting lambda expression. For example,

(letrec* ((y 'foo))
   (lambda (x) (cons y x)))
=> #<closure (x) (cons y x) ((y.foo))>

(The function term and the environment normally do not print in closures, but the :k 2 meta command can be use to make them visible.)

The association list '((y.foo)) is called the lexical environment of the lambda expression. When the lambda expression is applied to a value, its free variables get bound to the values stored in the environment:

((lambda (x) (cons y x) ((y.foo))) 'bar)  =>  '(foo.bar)

The lambda pseudo function mostly conforms to R5RS. It differs from R5RS in the following point:

5.16 LIST

(list x1 ... xN) => '(eval[x1] ... eval[xN])

List may have any number of arguments. It builds a new List containing its arguments in the specified order. The following rules apply:

(list)                => ()
(list 'x)             => '(x)
(list x)              => (eval[x])
(list 'x 'y)          => '(x y)
(list '(x y) '(y x))  => '((x y) (y x))
(list #t 123 '(y x))  => '(#t 123 (y x))
(list (lambda (x) x)) => (#<closure (x)>)

The list function conforms to R5RS.

5.17 NULL?

(null? x) => {#t,#f}

(Null? x) evaluates to #t, if x is equal to (), and to #f otherwise. It may easily be defined using the SketchyLISP function

(define null? (lambda (x) (eq? x ())))

but for reasons of efficiency, it has been implemented as a primitive function.

The null? function conforms to R5RS.

5.18 NUMBER?

(number? x) => {#t,#f}

(Number? x) evaluates to #t, if x is a numeric literal and otherwise to #f. The following rules apply:

(number? 123)    => #t
(number? 'x)     => #f
(number? ())     => #f
(number? '(x.y)) => #f
(number? '(x y)) => #f
(number? #t)     => #f

The number? function conforms to R5RS.

5.19 PAIR?

(pair? x) => {#t,#f}

Applications of pair? evaluate to #t, if x is a Pair, and otherwise to #f. The following rules apply:

(pair? 'x)     => #f
(pair? ())     => #f
(pair? '(x.y)) => #t
(pair? '(x y)) => #t
(pair? #t)     => #f
(pair? 123)    => #f

The pair? function conforms to R5RS.

5.20 PROCEDURE?

(procedure? x) => {#t,#f}

(Procedure? x) evaluates to #t, if x is a procedure and otherwise to #f. The following objects are procedures:

The following rules apply:

(procedure? cons)       => #t
(procedure? procedure?) => #t
(procedure? (lambda (x) x))           => #t
(procedure? (call/cc (lambda (k) k))) => #t
(procedure? 'x)         => #f
(procedure? ())         => #f
(procedure? '(x.y))     => #f
(procedure? '(x y))     => #f
(procedure? #t)         => #f
(procedure? 123)        => #f

The procedure? function mostly conforms to R5RS. It differs from R5RS in the following point:

5.21 QUOTE

(quote x) => 'x

Quote evaluates to its single argument. Because arguments to quote are passed to it call-by-name, (quote x) always results in 'X while x itself would reduce to eval[x]. Quote is used to quote expressions which are not to be evaluated:

(())                 => bottom
(quote (()))         => '(())
(car '(x.y))         => 'x
(quote (car '(x.y))) => '(car '(x.y))

The notation 'X is equal to (quote x):

'(x y) = (quote (x y)) => '(x y)

The quote function conforms to R5RS.