t3x.org / sketchy / sk04.html
SketchyLISP
Reference
  Copyright (C) 2006
Nils M Holm

4 Primitive Functions

4.1 Introduction

Primitive functions are those functions that cannot be expressed in terms of SketchyLISP in a practical way. These functions form the core of the language. Some of the primitives listed in this chapter are in fact pseudo functions. Their applications look like function applications, but their semantics differ from regular function applications in some subtle details, which will be outlined in this chapter. Most pseudo functions are called by name.

In this section a symbol is called a symbol only if it is quoted. Otherwise it is called a variable. Eg, 'x and => x denote symbols and x denotes a variable.

At the beginning of each function description, a generalized sample application is given. This application lists the types that the function expects by giving examples:

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

Unless such an example uses a variable as argument, it is an error to apply the function to a type other than that specified in the example:

(car 'non-pair) => bottom

When a variable is used as an argument, the function accepts any type:

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

The symbol bottom is used to denote an undefined value. A value is undefined, if it does not have a unique normal form. This does not imply that a reduction that results in bottom terminates with an error. It rather says that the result of the reduction should be considered invalid.

As an example, assume that both the symbols x and y are bound to the numeric value 457. Given these bindings, the expression

(eq? x y)

may reduce to #t or #f, since the identity of instances of numbers is arguable. Because the result is not predictable, it must be considered undefined.

4.2 Bindings and Definitions

4.2.1 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.

4.2.2 define

(define x y) => #<void>

Define introduces the given symbol x and then binds the normal form of y to that symbol:

(define x y) => #<void> ; x := eval[y]

The notation x := y denotes that the value of y is bound to x globally. A binding is global, if it is visible in an entire program.

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 (see letrec).

In addition to the above notation for defining function, SketchyLISP also supports the R5RS notation:

(define (f x) t)      =  (define f (lambda (x) t))
(define (f x . y) t)  =  (define f (lambda (x . y) t))
(define (f . x) t)    =  (define f (lambda x t))

Applications of define may not occur inside of other expressions.

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

4.2.3 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 closure. 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 :closure-form 2 meta command can be used to make them visible.)

The association list '((y . foo)) is called the lexical environment of the closure. When a closure 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:

4.2.4 let

(let ((x1 v1) ... (xN vN)) z) => eval[z]

Let binds each symbol xI to eval[vI], 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 let expression.

Let first evaluates all values v1..vN and then binds their normal forms to the symbols x1..xN. Therefore, symbols in the values of let are either unbound or bound in the outer context of that let:

(let ((x 'outer))
  (let ((x 'inner)
	(y x))
    y))
=> outer

Except for its syntax, the let construct is perfectly equal to the application of a lambda function:

(let ((x 3) (y 4))  =  ((lambda (x y) (* x y))
  (* x y))                3 4)

The let pseudo function conforms to R5RS.

4.2.5 letrec

(letrec ((x1 v1) ... (xN vN)) z) => eval[z]

Letrec binds each symbol xI to eval[vI], 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 is basically equal to let, but after binding its values to its arguments, it fixes recursive bindings using recursive-bind. Hence it can be used to bind recursive or even 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))
        (even-p '(i i i))))
=> (#t #f)

It is an error for a binding of letrec to refer to the value of another binding of the same letrec:

(letrec ((foo 'outer-value))
  (letrec ((foo 'inner-value)
           (bar foo))
    bar))
=> bottom

The letrec pseudo function conforms to R5RS.

4.2.6 package

(package ['x]]) => x

The package pseudo function is used to protect global symbols from re-definition. The symbols to be protected (which are typically created using define) are enclosed in two applications of package. The first application creates a new package which is to contain the protected symbols. The second one closes the package. An opening application names the package using its argument. Closing applications have no arguments.

When multiple packages define equal symbols, the currently open package is always searched first. The order of searching the other packages is unspecified. When no user-defined package is open, an initial package called the default package is open.

The following program demonstrates the use of package:

(define foo #t)         ; protect package
(package 'foo)          ; create and/or open package foo
(define bar 'baz)
(define (f) bar)
(package)               ; close package foo

(f) => baz
(define bar 'goo)       ; re-define bar
bar => goo             ; bar is re-defined as expected
(f) => baz             ; f of foo still returns bar of foo

Caveats

Packages break the identity of symbols:

(package 'p)
(define (f) 'unique-name)
(package)
(define unique-name #t)
(f) => 'unique-name
(eq? 'unique-name (f)) => #f

There is normally no need to use package in user-level code. It was added to protect some internal functions of the SketchyLISP implementation from re-definition.

The package pseudo function is specific to SketchyLISP.

4.2.7 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 pseudo function conforms to R5RS.

4.2.8 recursive-bind

(recursive-bind env) => env

The recursive-bind function takes an environment having the form of a association list (alist) as its argument and fixes up all recursive references in that alist. It evaluates to the given environment with all recursive references resolved.

Explanation

The alist may contain a binding to a recursive zero-argument lambda function f. Because applicative LISP cannot create cyclic stuctures, the environment of f binds f to an unspecific value:

((f . (lambda () (f) ((f . #<void>)))))

Therefore any use of f would result in an application of a non-function:

(f)
-> ((lambda () (f) ((f . #<void>))))
-> (#<void>)
=> bottom

To make the recursive function f work, the binding of f in the lexical environment of f must be bound to the value of f in the outer environment - the environment passed to recursive-bind. In other words: the local definition of f must bind to f itself. This is exactly what recursive-bind does:

(recursive-bind '((f . (lambda () (f) ((f . (void)))))))
=> ((f . (lambda () (f) ((f . (lambda () (f) ((f . ...))))))))

Recursive-bind is capable of resolving mutually recursive defintions as well.

Notes

The structure created by recursive-bind is potentially self-referential and hence infinite. Do not try to print it.

The creation of recursive bindings is normally done using letrec. Recursive-bind is a conceptual function that serves no other purpose than facilitating the implementation of metacircular interpreters.

The recursive-bind function is specific to SketchyLISP.

4.2.9 require

(require "file") => {#t,#f}

Require loads a package that is required by the program or package following in the input stream. It reads definitions from the given file and reduces them to their normal forms. Normal forms are not echoed.

Require reads the package only if it has not been required before. It returns #t after actually reading a package and #f when the package already was present.

To determine whether a package already has been required, require tests whether the basename of the given file is a symbol with a value. The basename is the name of the given file with its directory and suffix removed:

(require "src/unlet.scm") ; "unlet" is the basename

If a file that is being loaded using require loads other files using require, the files loaded by the nested application will be searched in the directory of the original file. Here is an example:

(require "lib/foo.scm")

will load the file foo.scm from the directory lib, so all files loaded by foo.scm will be opened inside of lib. Hence the command

(require "bar.scm")

inside of lib/foo.scm will in fact load lib/bar.scm.

Require resolves path names beginning with a "~/" prefix by replacing the "~" of that prefix with the value of the $HOME environment variable.

The require function is specific to SketchyLISP.

4.2.10 void

(void) => #<void>

The void function evaluates to an invalid value. By binding a symbol to (void), the binding of that symbols becomes unspecific. That is, the symbol becomes unbound:

(define x 'foo)
x => foo
(define x (void))
x => bottom

The void function is specific to SketchyLISP.

4.3 Control

4.3.1 and

(and expr...) => expr#f

And evaluates the given expressions from the left to the right and returns the value of the first expression reducing to #f. When an expression reduces to logical falsity, and returns immediately and does not evaluate any further expressions. When no argument of and evaluates to #f, the value of the application of and is equal to the value of the last expression. The following rules apply:

(and)           => #t
(and 'foo)      => foo
(and #f)        => #f
(and #t 'foo)   => foo
(and #t #f)     => #f
(and #f 'foo)   => #f
(and #f #f)     => #f
(and 'foo 'bar) => bar

The and pseudo function conforms to R5RS.

4.3.2 apply

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

Apply applies the function fun to the arguments contained in the list list. The additional arguments a1...aN are optional. 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 (lambda () 'foo) '()) => foo
(apply cons '(a))            => bottom

If any additional arguments a1...aN are specified, these are consed to the argument list list before applying fun to it:

(apply cons 1 '(2))       => (1 . 2)
(apply list 1 2 3 '(4 5)) => (1 2 3 4 5)

The apply function conforms to R5RS.

Note: SketchyLISP's apply may be applied to special form handlers such as and, define, etc, which R5RS does not allow. If you want the R5RS behaviour, use the strict-apply meta command.

4.3.3 begin

(begin expr1 ... exprN) => eval[exprN]

Begin reduces each of the given expressions to their normal form. Evaluation takes place in the order of occurrence. The normal form of each but the last expression is discarded. The normal form of an application of begin is the normal form of its last argument.

The only effect of begin is to make sure that the given expressions are reduced in the given order. In fact,

(begin x1 x2 x3)

is just a shorter and more readable form of

((lambda (x) x3)
  ((lambda (x) x2)
    ((lambda () x1))))

Because begin discards most of its results, it is mostly used to group expressions with side effects (see input/output primitives).

(begin)          => #<void>
(begin 'foo)     => foo
(begin 'a 'b 'c) => c

The begin function conforms to R5RS.

4.3.4 call/cc

(call/cc fun) => expr

(Call/cc fun) captures the current continuation and passes it to fun. Fun must be a function of one argument. The following conversion takes place:

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

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

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

the (current) continuation of

(call/cc (lambda (k) '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 captured continuation. The application of call/cc in the restored context reduces to 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)

The current continuation of the application of a captured continuation is discarded, so:

(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 chapter - 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

Note that this extension applies to SketchyLISP, but not to Scheme.

Once captured, 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:

(letrec ((x (call/cc (lambda (k) (cons 'foo k)))))
  (let ((v (car x))
        (k (cdr x)))
    (cond ((eq? v 'foo) (k (cons 'bar k)))
          ((eq? v 'bar) (k (cons 'baz k)))
          (#t v))))
=> baz

The call/cc function conforms to R5RS.

4.3.5 cond

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

The arguments of cond are passed to it call-by-name. Each argument of cond must be a clause of the form

(Pj Ej)

where Pj is interpreted as a truth value.

Cond first evaluates P1. If it reduces to a true value (something other than #f), cond evaluates E1. In this case, E1 is the normal form of the entire cond expression. Otherwise cond does not evaluate E1 and proceeds with the following arguments until a clause with Pj=/=#f is found. The following rules apply:

(cond (x y) (x2 y2)) => eval[y], if eval[x]=/=#f
(cond (x y) (x2 y2)) => (cond (x2 y2)), if eval[x]=#f
(cond (x y))         => bottom, if eval[x]=#f

The cond pseudo function mostly conforms to R5RS. It differs from the R5RS version in this point:

4.3.6 if

(if p eT eF) => e

If first evaluates the predicate p. If p evaluates to a true value, if evaluates eT and returns it. Otherwise if returns the normal form of eF.

If is just a short form of cond:

(if e1 e2 e3)  =  (cond (e1 e2) (#t e3))

The following rules apply:

(if #t 'true 'false) => true
(if #f 'true 'false) => false

The if pseudo function mostly conforms to R5RS. It differs from the R5RS version in this point:

4.3.7 or

(or expr...) => expr#t

Or evaluates the given expressions from the left to the right and returns the value of the first expression reducing to logical truth (a value other than #f). When an expression reduces to logical truth, or returns immediately and does not evaluate any further expressions. When no argument of or evaluates to truth, the value of the application of or is equal to the value of the last expression. The following rules apply:

(or)           => #f
(or 'foo)      => foo
(or #f)        => #f
(or #t 'foo)   => #t
(or #t #f)     => #t
(or #f 'foo)   => foo
(or #f #f)     => #f
(or 'foo 'bar) => foo

The or pseudo function conforms to R5RS.

4.4 Composition and Decomposition

4.4.1 car

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

(Car x) evaluates to the car part of x. X must be a pair. The following rules apply:

(car '(x . y))  => x
(car '(x y))    => x
(car '(x))      => x
(car ())        => bottom
(car 'non-pair) => bottom

The car function conforms to R5RS.

4.4.2 cdr

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

(Cdr x) evaluates to the cdr part of x. X must be a pair. The following rules apply:

(cdr '(x . y))  => y
(cdr '(x y))    => (y)
(cdr '(x))      => ()
(cdr ())        => bottom
(cdr 'non-pair) => bottom

The cdr function conforms to R5RS.

4.4.3 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.

4.5 Predicates

4.5.1 char?

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

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

(char? #\x)       => #t
(char? #\space)   => #t
(char? #\\)       => #t
(char? 'non-char) => #f

The char? function conforms to R5RS.

4.5.2 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 the same symbol or they are both the same boolean literal or they are both empty lists. 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
(eq? #\x #\x)       => bottom
(eq? "foo" "foo")   => bottom

The eq? function conforms to R5RS.

4.5.3 null?

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

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

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

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

The null? function conforms to R5RS.

4.5.4 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? -123)         => #t
(number? +123)         => #t
(number? 'non-integer) => #f

The number? function conforms to R5RS.

4.5.5 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 . y))  => #t
(pair? '(x y))    => #t
(pair? ())        => #f
(pair? 'non-pair) => #f

The pair? function conforms to R5RS.

4.5.6 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)     => #f
(procedure? (lambda (x) x))           => #t
(procedure? (call/cc (lambda (k) k))) => #t
(procedure? 'non-procedure)           => #f

The procedure? function conforms to R5RS.

4.5.7 string?

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

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

(string? "some text") => #t
(string? "")          => #t
(string? 'non-string) => #f

The string? function conforms to R5RS.

4.5.8 symbol?

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

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

(symbol? 'foo)         => #t
(symbol? 'symbol?)     => #t
(symbol? symbol?)      => #f
(symbol? "non-symbol") => #f

The symbol? function conforms to R5RS.

4.6 Type Conversion

4.6.1 char->integer

(char->integer #\c) => integer

The char->integer function evaluates to an integer whose value is equal to the ASCII code of its argument. The following rules apply:

(char->integer #\a)       => 97
(char->integer #\A)       => 65
(char->integer #\space)   => 32
(char->integer #\\)       => 92
(char->integer 'non-char) => bottom

The char->integer function conforms to R5RS.

4.6.2 integer->char

(integer->char 123) => char

The integer->char function evaluates to a char literal whose ASCII code is equal to its argument. The argument must be in the range 0..127. The following rules apply:

(integer->char 97)           => #\a
(integer->char 65)           => #\A
(integer->char 32)           => #\space
(integer->char 92)           => #\\
(integer->char 'non-integer) => bottom
(integer->char -1)           => bottom
(integer->char 128)          => bottom

The integer->char function conforms to R5RS.

4.6.3 integer->list

(integer->list 123) => list

The integer->list function evaluates to a list containing the digits and, if one exists, the prefix of the given integer. Digits are represented by the symbols 0d through 9d. The following rules apply:

(integer->list 0)            => (0d)
(integer->list 123)          => (1d 2d 3d)
(integer->list -5)           => (- 5d)
(integer->list +7)           => (+ 7d)
(integer->list 'non-integer) => bottom

The integer->list function is specific to SketchyLISP.

4.6.4 list->integer

(list->integer list) => integer

The list->integer function evaluates to an integer composed of the digits and the prefix (if any) contained in the given list. The list must contain a positive number of digits, and it may contain a sign of the form + or - at its first position. Digits are represented by the symbols 0d through 9d. The following rules apply:

(list->integer '(0d))        => 0
(list->integer '(1d 2d 3d))  => 123
(list->integer '(- 7d))      => -7
(list->integer '(+ 5d))      => +5
(list->integer '(non-digit)) => bottom
(list->integer '())          => bottom
(list->integer '(-))         => bottom
(list->integer '(+ + 1d))    => bottom
(list->integer 'non-list)    => bottom

The list->integer function is specific to SketchyLISP.

4.6.5 list->string

(list->string list) => string

The list->string function evaluates to a string literal that is composed of the characters contained in the list passed to it. The list must contain objects of the type char exclusively. The following rules apply:

(list->string '(#\X))             => "X"
(list->string '(#\t #\e #\x #\t)) => "text"
(list->string '())                => ""
(list->string '(#\"))             => "\""
(list->string '(non-char))        => bottom
(list->string 'non-list)          => bottom

The list->string function conforms to R5RS.

4.6.6 string->list

(string->list "xyz") => list

The string->list function evaluates to a list containing the same characters as the string passed to it. Each character of the string will be represented by an individual char literal in the resulting list. The following rules apply:

(string->list "X")         => (#\X)
(string->list "text")      => (#\t #\e #\x #\t)
(string->list "")          => ()
(string->list "\"")        => (#\")
(string->list 'non-string) => bottom

The string->list function conforms to R5RS.

4.6.7 string->symbol

(string->symbol "xyz") => xyz

The string->symbol function evaluates to a symbol that is composed of the characters of the given string argument. The following rules apply:

(string->symbol "foo")       => foo
(string->symbol "FOO")       => FOO
(string->symbol " ")         => 
(string->symbol "")          => bottom
(string->symbol 'non-string) => bottom

Notes

(1) String->symbol may be used to create symbols that cannot be accessed by programs, such as symbols containing white space, symbols containing upper case letters, and symbols containing special characters like parentheses, dots, semicolons, etc.

(2) Symbols containing spaces lead to an ambiguity, as demonstrated below. Therefore future implementation of SketchyLISP may refuse to create such symbols.

(string->symbol "foo bar")                  => foo bar
(symbol->string (string->symbol "foo bar")) => "foo bar"
(symbol->string 'foo bar)                   => bottom

The string->symbol function mostly conforms to R5RS.
The R5RS variant allows the creation of empty symbols.

4.6.8 symbol->string

(symbol->string 'xyz) => "xyz"

The symbol->string function evaluates to a string that is composed of the characters of the literal symbol passed to it. The following rules apply:

(symbol->string 'foo)         => "foo"
(symbol->string 'FOO)         => "foo"
(symbol->string "non-symbol") => bottom

The symbol->string function conforms to R5RS.

4.7 Char Functions

4.7.1 char-ci<?

(char-ci<? #\a #\B) => #t

The char-ci<? function compares two chars and returns #t if the first of the two chars comes first in the ASCII character set. Otherwise it returns #f. When comparing letters, their case is ignored. The following rules apply:

(char-ci<? #\a #\b)       => #t
(char-ci<? #\a #\B)       => #t
(char-ci<? #\A #\b)       => #t
(char-ci<? #\A #\B)       => #t
(char-ci<? #\b #\a)       => #f
(char-ci<? 'non-char #\x) => bottom
(char-ci<? #\x 'non-char) => bottom

The char-ci<? function conforms to R5RS.

4.7.2 char-ci=?

(char-ci=? #\C #\c) => #t

The char-ci=? function compares two chars and returns #t if the two chars are equal. Otherwise it returns #f. When comparing letters, their case is ignored. The following rules apply:

(char-ci=? #\a #\a)       => #t
(char-ci=? #\a #\A)       => #t
(char-ci=? #\A #\a)       => #t
(char-ci=? #\A #\A)       => #t
(char-ci=? #\a #\b)       => #f
(char-ci=? 'non-char #\x) => bottom
(char-ci=? #\x 'non-char) => bottom

The char-ci=? function conforms to R5RS.

4.7.3 char<?

(char<? #\a #\b) => #t

The char<? function compares two chars and returns #t if the first of the two chars comes first in the ASCII character set. Otherwise it returns #f. The following rules apply:

(char<? #\a #\b)       => #t
(char<? #\A #\B)       => #t
(char<? #\a #\B)       => #f
(char<? #\b #\a)       => #f
(char<? 'non-char #\x) => bottom
(char<? #\x 'non-char) => bottom

The char<? function conforms to R5RS.

4.7.4 char=?

(char=? #\c #\c) => #t

The char=? function compares two chars and returns #t if the two chars are equal. Otherwise it returns #f. The following rules apply:

(char=? #\a #\a)       => #t
(char=? #\A #\A)       => #t
(char=? #\a #\A)       => #f
(char=? #\a #\b)       => #f
(char=? 'non-char #\x) => bottom
(char=? #\x 'non-char) => bottom

The char=? function conforms to R5RS.

4.8 Numeric Functions

4.8.1 n+

(n+ num1 num2) => num3

The n+ function implements a multi-digit decimal full adder. It adds two natural numbers num1 and num2 (no signs are allowed!) and returns their sum num3. Precision is arbitrary, no overflow can occur.

N+ is used to implement the more general bignum arithmetic function like +, *, etc. The following rules apply:

(n+ 12 9)            => 23
(n+ 12 0)            => 12
(n+ 12 +1)           => bottom
(n+ -12 1)           => bottom
(n+ 'non-number 123) => bottom
(n+ 123 'non-number) => bottom

The n+ function is specific to SketchyLISP.

4.8.2 n-

(n- num1 num2) => num3

The n- function implements a multi-digit decimal subtractor. It subtracts the number num2 from num1, returning their difference num3. Both of its arguments must be natural numbers without any signs. N- cannot create negative results, so num2 must be less than or equal to num1. This function may subtract number of any size.

N- is used to implement the more general bignum arithmetic function like +, *, etc. The following rules apply:

(n- 12 9)            => 3
(n- 12 0)            => 12
(n- 9 12)            => bottom
(n- 12 +1)           => bottom
(n- -12 1)           => bottom
(n- 'non-number 123) => bottom
(n- 123 'non-number) => bottom

The n- function is specific to SketchyLISP.

4.8.3 n<

(n< num1 num2) => {#t,#f}

The n< function implements a multi-digit decimal comparator. It compares the two natural numbers num1 and num2, returning #t if num1 is less than num2. In case num1 is greater than or equal to num2, it returns #f. N< may compare number of any size.

The n< function is used to implement the more general bignum comparison function like <, >=, etc. The following rules apply:

(n< 9 12)            => #t
(n< 12 9)            => #f
(n< 12 +9)           => bottom
(n< -12 9)           => bottom
(n< 'non-number 123) => bottom
(n< 123 'non-number) => bottom

The n< function is specific to SketchyLISP.

4.9 String Functions

4.9.1 string-append

(string-append str1 ...) => strN

String-append returns a new string that contains the concatenation of the strings passed to it as arguments. The following rules apply:

(string-append)             => ""
(string-append "xyz")       => "xyz"
(string-append "x" "y" "z") => "xyz"
(string-append 'non-string) => bottom

The string-append function conforms to R5RS.

4.9.2 string-length

(string-length str) => num

The string-length function returns the number of characters contained in the string str. The following rules apply:

(string-length "")          => 0
(string-length "xyz")       => 3
(string-length 'non-string) => bottom

The string-length function conforms to R5RS.

4.9.3 string-ref

(string-ref str num) => char

String-ref extracts the character at position num from the string str. The position of the first character of a string is zero. Num may not be negative and it may not be longer than the length of str minus one. String-ref returns the extracted character. The following rules apply:

(string-ref "x" 0)    => #\x
(string-ref "xyz" 0)  => #\x
(string-ref "xyz" 2)  => #\z
(string-ref "xyz" 3)  => bottom
(string-ref "xyz" -1) => bottom
(string-ref 'non-string 0)     => bottom
(string-ref "xyz" 'non-number) => bottom

The string-ref function conforms to R5RS.

4.9.4 substring

(substring str1 num1 num2) => str2

The substring primitive returns a new string containing a substring of str1. The substring to be extracted is described by the arguments num1 and num2 as follows: Num1 is the position of the first character to be extracted, and num2 is the position of the first character not to be extracted. Positions start at zero. The following assertion must hold:

(<= num1 num2 (string-length str1))

The following rules apply:

(substring "" 0 0)    => ""
(substring "xyz" 0 0) => ""
(substring "xyz" 0 3) => "xyz"
(substring "xyz" 0 1) => "x"
(substring "xyz" 1 3) => "yz"
(substring 'non-string 0 0)     => bottom
(substring "xyz" 'non-number 0) => bottom
(substring "xyz" 0 'non-number) => bottom

The substring function conforms to R5RS.

4.10 Input/Output

4.10.1 delete-file

(delete-file str) => {#t,#f}

(delete-file str) deletes the file named in str. It returns #t, if the given file could be deleted and otherwise #f. Possible reasons why a file could not be deleted include

The following rules apply:

(delete-file "foo")       => #t     ; file foo deleted
(delete-file "foo")       => #f     ; file foo not deleted
(delete-file 'non-string) => bottom

The delete-file function is specific to SketchyLISP.

4.10.2 display

(display expr) => expr

The display function writes a nicely formatted copy of expr to the output stream. Formatting includes the removal of quotation marks of strings and the #\ prefix of chars. The #\space and #\newline objects print as a blank character and a newline character. Display returns (void).

Display does not print the external representation of an expression. Use write to print unambiguous representations of expressions.

Output is formatted as follows:

(display 'foo)      writes  foo
(display #\a)       writes  a
(display 123)       writes  123
(display "hello")   writes  hello
(display "\"hi\"")  writes  "hi"
(display '(x . y))  writes  (x . y)
(display #\space)   writes
(display #f)        writes  #f
(display (lambda (x) x))  writes  #<closure (x)>
(display car)       writes  #<primitive car>
(display cond)      writes  #<special cond>

The display function conforms to R5RS.

4.10.3 eof-object?

(eof-object? expr) => {#t,#f}

(Eof-object? x) evaluates to #t, if x is the EOF object and otherwise to #f. The EOF object is returned by the read and read-char functions when attempting to read beyond the end of the input stream.

Because the EOF object has no unambiguous external representation, the following examples use the one defined here:

(define eof
  (begin
    (with-output-to-file "empty-file" (lambda () ()))
    (with-input-from-file "empty-file" read)))

Given above definition, the following rules apply:

(eof-object? eof)               => #t
(eof-object? 'any-other-object) => #f

The eof-object? function conforms to R5RS.

4.10.4 read

(read) => expr

(Read) reads the external representation of an expression from the input stream, converts it to internal representation, and returns it. After reading a single expression, read returns. Any text following the expression in the input stream will be left there. Read is capable of reading any unambiguous external representation that was written by write.

When read is applied to an input stream that does not hold any input, it returns the EOF object which can be checked using the eof-object? function.

The top level loop of the SketchyLISP interpreter uses read to parse programs.

Here are sample applications of read:

(read)foo      => foo
(read) foo     => foo
(read) 'foo    => 'foo
(read) foo bar => foo     ; bar is left in the input stream
(read) "hello" => "hello"
(read) #\x     => #\x
(read) #\space => #\space
(read) #\;     => #\;
(read) #t      => #t
(read) (x . y) => (x . y)
(read) #<foo>  => bottom   ; unreadable object

The read function conforms to R5RS.

4.10.5 read-char

(read-char) => char

(Read-char) reads a single character from the input stream and returns a matching char object. Unlike read it does not parse its input, but reads raw characters. If multiple characters are available from the input stream, read-char returns only the first one and leaves the remaining input in the stream. When no characters are available from the input stream, the EOF object is returned.

Here are sample applications of read-char:

(read-char)x  => #\x
(read-char) x => #\space   ; x is left in the input stream
(read-char)(  => #\(
(read-char);  => ;
(read-char)
              => #\newline

The read-char function conforms to R5RS.

4.10.6 with-input-from-file

(with-input-from-file str fun) => expr

The with-input-from-file function opens the file named in the string str and connects the input stream to that file. In this context, it applies the function fun, which must be a function of zero arguments. As soon as the function returns, the file is closed and the input stream is connected to the source that was in effect before.

All applications of read or read-char inside of fun will read from the given file. Applications of with-input-from-file evaluate to the value of (fun).

In case the specified file does not exist, applications of with-input-from-file evaluate to bottom.

A sample application of this function follows. The file foo is assumed to contain the text (foo "hello" 5).

(with-input-from-file "foo" read) => (foo "hello" 5)

The with-input-from-file function conforms to R5RS.

4.10.7 with-output-to-file

(with-output-to-file str fun) => expr

The with-output-to-file function opens the file named in the string str and connects the output stream to that file. In this context, it applies the function fun, which must be a function of zero arguments. As soon as the function returns, the file is closed and the output stream is connected to the file or device that was in effect before.

All applications of write or display inside of fun will write to the given file. Applications of with-output-to-file evaluate to the value of (fun).

In case the file specified in str already exists, its content will be overwritten.

The following sample application of this function writes the expression (foo "hello" 5) to the file foo.

(with-output-to-file "foo"
  (lambda () (write '(foo "hello" 5))))
=> (foo "hello" 5)

The with-output-to-file function conforms to R5RS.

4.10.8 write

(write expr) => expr

The write function writes the external representation of expr to the output stream. It returns (void).

Unambiguous external representation written by write may be read back using the read function. The original expression a and the re-read expression b are gurranteed to be equal in the sense of (equal? a b). They will not be identical, though (see eq?).

Output is formatted as follows:

(write 'foo)       writes  foo
(write #\a)        writes  #\a
(write 123)        writes  123
(write "hello")    writes  "hello"
(write "\"hi\"")   writes  "\"hi\""
(write '(x . y))   writes  (x . y)
(display #\space)  writes  #\space
(write #f)         writes  #f
(write (lambda (x) x))  writes  #<closure (x)>
(write car)        writes  #<primitive car>
(write cond)       writes  #<special cond>

The write function conforms to R5RS.