Data types
Cons Cells are the central data type in classic Lisp used both as dotted pairs (a . b)
and general lists (a b)
. For an overview of cons cells and how to use them see [1], [3], or http://cs.gmu.edu/~sean/lisp/cons/
. Note that dotted pairs require spaces around the period; (a.b)
is a list containing the symbol a.b
, not a cons cell with car of a
and cdr of b
.
Symbols are simple identifiers, e.g. function-name
. Symbols
follow the follow 3 simple rules:
- can only contain graphic characters (i.e. no control characters)
-
can not contain any of the characters: ();,"’` []{} - can not contain whitespace
Typically, -
is used to separate words in a symbol, _
is used in special symbols (such as system use) to separate words and as a prefix and suffix. The characters ?
, !
, and *
are typically used as the final character of a function name to denote:
? a predicate, e.g. nil?
! a mutating function (changes the argument rather than returning a modified copy), e.g. set-car!
* a variant of the primary function, e.g. flatten
(which does a one level flattening of a list) and flatten*
(which is a recursive flatten
)
If a symbol ends with :
it is what is called a naked symbol. It has no value other than itself. If it is evaluated, the result is the symbol itself. This feature is utilized by frames.
Strings are any sequence of characters other than "
enclosed by a pair of "
, e.g. "string"
. If you need to have "
in a string, use \"
.
Integers are sixtyfour bit signed integers. Both decimal and hexadecimal formats are supported. E.g. 26
, 0x1a
, 0x1A
. As of V1.0, the proper Scheme syntax for hex numbers is supported. E.g.: #x1a
. As well, Scheme binary numbers are supported now. E.g. #b1001001001
.
Floats are Go float32
numbers. Accordingly they are signed. All arithmetic functions with the exception of modulus work as expected for both integers and floats. Numbers are coerced to floats as required, specifically if any arguments are float, all are converted to float and the result will be a float.
Booleans represent true and false. nil
, ()
, and false
are all logically false, everything else is logically true. Boolean literals are #t
and #f
for true and false, respectively.
Functions are user defined procedures. They are covered in detail later.
Objects allow you to encapsulate a Go object (struct) in a Lisp data object. There is no way to do this from Lisp itself, but is useful when writing primitive functions (see below).
Ports provide access to files for reading and writing.
Bytearrays are simply objects that encapsulate []byte
objects. The difference is that there is syntactic support for them. Use square braces surrounding a list of numbers between 0 and 255, inclusive. For example: [1 2 3 4 5]
. That format will parse to an object
containing a the Go bytearray (i.e. []byte
). Bytearrays evaluate to themselves. There are also functions for doing bytearray manipulation.
Frames are sets of named slots that hold arbitrary values, including functions. Frames can inherit from other frames in a prototypical manner. Frames are inspired by the languages Self [4] and Newtonscript [5].
Primitives are just as they are in Lisp or Smalltalk: functions written in Go and exposed as functions in Lisp. The combinaion of primitives and objects allow you to integrate with the underlying Go program. Special forms are almost identical to primitives, except that they use normal evaluation order instead of applicative order which functions and primitives use.
Control Structures
(quote sexpr)
‘sexpr
Suppresses evaluation of the expression.
(quote (+ 1 2)) ⇒ (+ 1 2)
There is a shortcut for quote that uses the single quote:
'(+ 1 2) ⇒ (+ 1 2)
(cond (predicate sexpr…)…)
Each predicate is evaluated in order until one results in true value. The expressions associated with this predicate are then evaluated in order, and the result of the cond
is the result of the last evaluation. If all predicates evaluate to false values, the value of cond
in indeterminate. If, however, the final predicate is the symbol else
the expressions associated with it are evaluated, and the value of cond
is the value of the last evaluation.
(cond ((> 3 2) 'greater)
((< 3 2) 'less)) ⇒ greater
(cond ((> 3 3) 'greater)
((< 3 3) 'less)
(else 'equal)) ⇒ equal
(case target ((value…) sexpr…)… [(else sexpr…)])
case
chooses code to evaluate based on the value of evaluating target. Any number of expressions can be associated with each target value. None of the value expressions are evaluated.
(define (test-func x)
(case x
((0) "zero")
((1) "one")
((2 3) "some")
(else "unknown")))
(test-func 0) ⇒ "zero"
(test-func 1) ⇒ "one"
(test-func 2) ⇒ "some"
(test-func 3) ⇒ "some"
(test-func 5) ⇒ "unknown"
Note that if a value expression contains a symbol(s) they do not need to be quoted.
(case name
((a b) 1)
((c d) 2))
(if condition true-clause)
This is a special case of cond
that is more familiar to programmers used to Algol style languages. if
has two forms, one that conditionally evaluates a single sexpr (see below about begin
which provides a way to use multiple sexprs in this context).
Iff condition evaluates to logically true, true-clause is evaluated and the result of that is the result of the if
form, otherwise nil is the result of the form.
(if (< 1 2)
"less") ⇒ "less"
(if (< 2 2)
"less") ⇒ nil
(if condition true-clause false-clause)
If condition evaluates to logically true, true-clause is evaluated and the result of that is the result of the if
form, otherwise the false-clause is evaluated and is the result of the form.
(if (< 1 2)
"less"
"not less") ⇒ "less"
(if (< 2 2)
"less"
"not less") ⇒ "not less"
(when condition sexpr…)
If condition evaluates to logically true
, the sequence of sexpr is evaluated and the result of the last one is the result of the when
form, otherwise nil
is the result.
(when (> x 5)
(write-line "greater")
(+ x 2))
The above is equivalent to the following, but is simpler and clearer.
(if (> x 5)
(begin (write-line "greater")
(+ x 2)))
(unless condition sexpr…)
If condition
evaluates to logically false
, the sequence of sexpr is evaluated and the result of the last one is the result of the unless
form, otherwise nil
is the result.
(unless (> x 5)
(write-line "greater")
(+ x 2))
The above is equivalent to the following, but is simpler and clearer.
(if (> x 5)
()
(begin (write-line "greater")
(+ x 2)))
(define symbol value)
Evaluates the value expression and binds it to the symbol, returning the value.
(define x (+ 2 3)) ⇒ 5
x ⇒ 5
(begin sexpr…)
Evaluates the each sexpr in order, returning the result of the last one evaluated. Used in a context that allows a single sexpr but you need multiple. E.g.
(if (predicate)
(begin
(do-something)
(do-something-else)))
(let ((name value)…) sexpr…)
Create a local scope and bindings for evaluating a body of code. The first argument is a list of bindings. Each binding is a raw symbol (doesn’t get evaluated) that is the name to be bound, and a value (which is evaluated). These bindings are added to a scope that is local to the let. The body is evaluated in this local scope. The value of the let
is the last evaluation result. E.g.
(let ((x 1)
(y 2))
(+ x y)) ⇒ 3
Bindings DO NOT cascade. I.e. each binding’s value is evaluated in the context of the scope where the let
appears. E.g.
(define x 39)
(let ((x 1)
(y (+ x 2)))
(+ x y)) ⇒ 42
When the value of y
is computed, x
is taken from the outside environment, and there it is bound to 39, so y
is bound to 41. When the addition is evaluated in the body of the let
, the let
-bound value of x
(which is 1) is used, so the result is 42.
As mentioned above, let
takes a sequence of sexprs as it’s body. In effect the body of the let
is an implicit begin
.
(let* ((name value)…) sexpr…)
NOTE: this used to be let
and there was no let*
In many cases this won’t matter, but in some it will. You might want to check any existing use of let
.
This is the same as let
EXCEPT that bindings do cascade. I.e. each binding’s value is evaluated in the context of the local scope. E.g.
(let ((x 1)
(y (+ x 2)))
(+ x y)) ⇒ 4
(letrec ((name value)…) sexpr…)
The variables are bound to fresh locations holding unassigned values, the inits are evaluated in the extended environment (in some unspecified order), each variable is assigned to the result of the corresponding init, the expressions are evaluated sequentially in the extended environment, and the value of the last expression is returned. Each binding of a variable has the entire letrec expression as its region, making it possible to define mutually recursive procedures.
GoLisp allows any of the inits to be omitted, in which case the corresponding variables are unassigned.
(letrec ((even? (lambda (n)
(if (zero? n)
#t
(odd? (- n 1)))))
(odd? (lambda (n)
(if (zero? n)
#f
(even? (- n 1))))))
(even? 88))
One restriction on letrec
is very important: it shall be possible to evaluated each init without assigning or referring to the value of any variable. If this restriction is violated, then it is an error. The restriction is necessary because Scheme passes arguments by value rather than by name. In the most common uses of letrec
, all the inits are lambda expressions and the restriction is satisfied automatically.
(let name ((name value)…) sexpr…)
MIT/GNU Scheme permits a variant on the syntax of let
called named let which provides a more general looping construct than do
, and may also be used to express recursions. GoLisp also supports this syntax.
Named let
has the same syntax and semantics as ordinary let
except that name is bound within the expressions to a procedure whose formal arguments are the variables and whose body is the expressions. Thus the execution of the expressions may be repeated by invoking the procedure named by name.
Here is an example:
(let loop
((numbers '(3 -2 1 6 -5))
(nonneg '())
(neg '()))
(cond ((null? numbers)
(list nonneg neg))
((>= (car numbers) 0)
(loop (cdr numbers)
(cons (car numbers) nonneg)
neg))
(else
(loop (cdr numbers)
nonneg
(cons (car numbers) neg)))))
⇒ ((6 1 3) (-5 -2))
(do ((name initial next)…) (test [sexpr…]) [sexpr…])
This is a general purpose iteration construct. There are three sections:
bindings This is similar to let
in that it defines names that can be used in the remainder of the scope of the do
. Like let
it is a list of lists, each starting with the binding name followed by the initial value of the binding. The difference is that this is followed by an expression that is evaluated at the beginning of each subsequent pass through the loop, and whose result is used as a new binding of the name.
termination This is a list whose first element is an expression which is evaluated before each pass through the loop (after rebinding the variables). If it evaluates to a truthy value, the remaining expressions are evaluated in turn. Looping ends and the result of the final evaluation is used as the value of the do
form.
body This is a sequence of expressions that are evaluated in order each time though the loop. This section can be empty.
Here’s an example of using do
to iteratively find the length of a list.
(do ((l some-list (cdr l))
(count 0 (+ 1 count)))
((nil? l) count))
(-> value sexpr|symbol…)
This creates a function chain. value (evaluated first) is used as the first argument to the first sexpr. The result of each sexpr is used as the first argument of the next, and the result of the final sexpr is the value of the ->
form. If a sexpr would take a single argument (which would be provided by the value or the result of the previous sexpr, just the function name can be used.
The form (-> 0 a b c)
is equivalent to (c (b (a 0)))
.
(-> 1 (+ 3) (- 2)) ⇒ 2 ; (- (+ 1 3) 2)
(-> 1 (+ 3) (- 2) str) ⇒ "2" ; (str (- (+ 1 3) 2))
(=> value sexpr|symbol…)
This operates similarly to ->
with two differences:
-
value (evaluated once at the beginning) is used as the initial argument to each function, and they are independent and do not pass results one to another.
-
value is the result of the form.
The expression
(=> 1 a b c)
is equivalent to
(begin
(a 1)
(b 1)
(c 1)
1)
and
(=> (+ x 1) a b c)
is the same as
(let ((y (+ x 1)))
(a y)
(b y)
(c y)
y)
Assignment
(set! name new-value)
The way to assign (i.e. rebind) a symbol. name is the symbol to be rebound. new-value is evaluated to arrive at the new value to be bound to. Use of set!
is frowned upon, and should not be used without thought. See Chapter 3 of [1] for a discussion of the pros and cons of assignment.
(define x 5)
(set! x 10)
x ⇒ 10
Assigning takes action in the closest scope in which the symbol is bound. In the following example, that’s the global scope, so when the let exits, x
still has the rebound value of 10
.
(define x 5)
(let ()
(set! x 10)
x) ⇒ 10
x ⇒ 10
In this example, x
is bound by the let, i.e. in the local context of the let, so the rebinding is done in the scope of the let and doesn’t extend beyond it.
(define x 5)
(let ((x 1))
(set! x 10)
x) ⇒ 10
x ⇒ 5
(set-car! cons-cell new-value)
Set the car
pointer of cons-cell.
(define a '(1 2))
(set-car! a 3)
a ⇒ (3 2)
(set-cdr! cons-cell new-value)
Set the cdr
pointer of cons-cell.
(define a '(1 2))
(set-cdr! a 3)
a ⇒ (1 . 3)
(set-nth! list n new-value)
Set the car
pointer of the nth cons cell of list. Numbering starts at 1.
(define a '(1 2 3 4))
(set-nth! a 3 0)
a ⇒ (1 2 0 4)
Functions
(lambda (param…) expr…)
Creates an anonymous function. This can then be used in a function call.
((lambda (x)
(+ x x))
5) ⇒ 10
lambda
creates a local environment at the point of it’s evaluation. This environment is attached to the resulting function, and any binding or symbol lookup starts in this local environment. For example, consider the following code (from [1], §3.2.2):
(define square (lambda (x)
(* x x)))
(define sum-of-squares (lambda (x y)
(+ (square x) (square y))))
(define f (lambda (a)
(sum-of-squares (+ a 1) (* a 2))))
Each of the three functions have their own associated definition environment pointer that points back to the global environment.
When a function is executed, the lambda
creates a new environment frame for the evaluation that (in this case) points back to the global environment. This scoping approach is what is described in §3 of [1].
Functions can also be named (i.e. bound to a symbol) by a special form of define:
(define (symbol param…) body)
symbol specifies the name (how you reference the function)
param>… parameters of the function, these are bound to the respective arguments when the function is called.
body the sequence of expressions that are evaluated in order when the function is called. The final evaluation result becomes the value of evaluation of the function.
(define (square x)
(* x x))
(define (sum-of-squares x y)
(+ (square x) (square y)))
(define (f a)
(sum-of-squares (+ a 1) (* a 2)))
(apply function sexpr…)
Apply the function that results from evaluating function to the argument list resulting from evaluating each sexpr.
Each initial sexpr can evaluate to any type of object, but the final one (and there must be at least one sexpr) must evaluate to a list.
(apply + 1 2 '(3 4)) ⇒ 10
(apply + '(1 2 3 4)) ⇒ 10
(eval sexpr)
Evaluate sexpr.
(eval '(+ 1 2 3 4)) ⇒ 10
(definition-of function)
Fetch the definition of function. This returns an expression that can be evaluated to define it. One use of this is to copy definitions to a source file.
(define (square x)
(* x x))
(definition-of square) ⇒ (define (square x) (* x x))
(define square (lambda (x)
(* x x)))
(definition-of square) ⇒ (define square (lambda (x) (* x x)))
Macros
Macros are for making syntactic extensions to Lisp.
(quasiquote sexpr)
`sexpr
This defines a template expression that can be filled in by unquote
and unquote-splicing
.
(unquote sexpr)
,sexpr
This evaluates sexpr and inserts the result into the template in place of the unquote
expression.
(unquote-splicing sexpr)
,@sexpr
This evaluates sexpr and inserts the result, which is assumed to be a list, into the template in place of the unquote-splicing
expression. The list itself is not inserted, rather the sequence of items in the list are.
`(a ,(+ 1 2) ,@(map abs '(4 -5 6)) b) ⇒ (a 3 4 5 6 b)
Quasiquote forms may be nested. Substitutions are made only for unquoted components appearing at the same nesting level as the outermost quasiquote. The nesting level increases by one inside each successive quasiquotation, and decreases by one inside each unquotation.
‘(a ‘(b ,(+ 1 2) ,(foo ,(+ 1 3) d) e) f) ⇒ (a ‘(b ,(+ 1 2) ,(foo 4 d) e) f)
(let ((name1 ’x)
(name2 ’y))
‘(a ‘(b ,,name1 ,’,name2 d) e)) ⇒ (a ‘(b ,x ,’y d) e)
(defmacro (symbol [param…]) sexpr)
Create a named macro:
symbol specifies the name (how you reference the macro)
param… parameters of the macro, these are bound to the respective arguments when the macro is invoked. NOTE that the arguments to a macro invocation are not evaluated, but are passed as is to the macro to do with as it wishes.
sexpr the template expression that is processed when the macro is invoked. The result of evaluating the processed template expression becomes the value of the macro’s invocation.
(defmacro (double x)
`(+ ,x ,x))
(double 5) ⇒ 10
(expand symbol [sexpr…])
Expands the macro named by symbol, passing the evaluated sequence of sexpr as arguments. NOTE: whereas invoking the macro (in the same way you invoke a function) expands and evaluates, expand
(as you would expect) only expands the macro, resulting in the expanded template sexpr. This can then be evaluated as desired.
(defmacro (double x)
`(+ ,x ,x))
(expand double 5) ⇒ (+ 5 5)
Builtin functions
Type checking
(atom? sexpr)
Returns whether sexpr is an atom, i.e. a number, string, boolean, or symbol.
(list? sexpr)
Returns whether sexpr is a list.
(pair? sexpr)
Returns whether sexpr is a pair, i.e. a list or a dotted pair.
(alist? sexpr)
Returns whether sexpr is an association list.
(nil? sexpr)
(null? sexpr)
Returns whether sexpr is an empty list, i.e. the nil value.
(notnil? sexpr)
(notnull? sexpr)
Returns whether sexpr is not an empty list, i.e. the nil value.
(symbol? sexpr)
Returns whether sexpr is a symbol.
(string? sexpr)
Returns whether sexpr is a string.
(number? sexpr)
Returns whether sexpr is a number.
(integer? sexpr)
Returns whether sexpr is an integer.
(float? sexpr)
Returns whether sexpr is a floating point number.
(function? sexpr)
Returns whether sexpr is a function.
(macro? sexpr)
Returns whether sexpr is a macro.
(frame? sexpr)
Returns whether sexpr is a frame.
(bytearray? sexpr)
Returns whether sexpr is a bytearray.
(port? sexpr)
Returns whether sexpr is a port.
Arithmetic
(+ number…)
Adds a series of numbers, e.g.
(+ 4) ⇒ 4
(+ 4 2) ⇒ 6
(+ 4 2 7) ⇒ 13
(+ 1.2 2.3) ⇒ 3.5
(+ -1.2 2.3) ⇒ 1.0999999
(- number…)
Sequentially subtracts a sequence of numbers, e.g.
(- 10 3) ⇒ 7
(- 10 3 4) ⇒ 3
(- 10 3 4 5) ⇒ -2
(- 2.3 1.2) ⇒ 1.0999999
(- 1.2 2.3) ⇒ -1.0999999
(* number…)
Multiplies a series of numbers, e.g.
(* 4) ⇒ 4
(* 4 2) ⇒ 8
(* 4 2 7) ⇒ 56
(* 1.2 2.3) ⇒ 2.76
(/ number…)
(quotient number…)
Sequentially divides a sequence of numbers. E.g.
(/ 30 2) ⇒ 15
(/ 30 2 3 ) ⇒ 5
(/ 30 4) ⇒ 7
(/ 30.0 4) ⇒ 7.5
(% numerator denominator)
(modulo numerator denominator)
Returns the remainder of division of two numbers. NOTE: modulus only works for integers. E.g.
(% 4 2) ⇒ 0
(% 4 3) ⇒ 1
(floor number)
Returns the greatest integer value less than or equal to number
. number
can be an integer or float. Return value is a float.
(floor 3.4) ⇒ 3.0
(floor -3.4) ⇒ -4.0
(floor 3) ⇒ 3.0
(ceiling number)
Returns the largest integer value greater than or equal to number
. number
can be an integer or float. Return value is a float.
(ceiling 3.4) ⇒ 4.0
(ceiling -3.4) ⇒ -3.0
(ceiling 3) ⇒ 3.0
(min list)
Returns the smallest value in list
, which has to be a list of numeric values.
(min '(1 2 3)) ⇒ 1
(max list)
Returns the largest value in list
, which has to be a list of numeric values.
(max '(1 2 3)) ⇒ 3
(succ integer)
Returns the successor to integer. I.e. the one after.
(succ -2) ⇒ -1 (succ 2) ⇒ 3
(pred integer)
Returns the predessor to integer. I.e. the one before.
(pred -2) ⇒ -3 (pred 2) ⇒ 1
(integer number)
Returns the integer value of number. If it is an integer, it is simply returned. However, if it is a float the integer part is returned. Note that this is implemented in go by casting the float32
to a int64
so converting negative floats gives a large integer.
(integer 5) ⇒ 5
(integer 5.2) ⇒ 5
(integer 5.8) ⇒ 5
(integer -1.0) ⇒ 4294967295
(float number)
Returns the float value of number. If it is a float, it is simply returned. However, if it is an integer the corresponding float is returned.
(float 5) ⇒ 5.0
Note that converting a float to a string for printing using the format %g
to use the minimum number of characters so 5.0
will actually print as 5
.
(number->string number [base])
Converts number (first converted to an integer) to a string, in the given base. Allowed bases are 2, 8, 10, and 16. If the base is omitted, 10 is used. No base prefixes (e.g. 0x
for base 16) are added.
(number->string 42) ⇒ "42"
(number->string 42 2) ⇒ "101010"
(number->string 42 8) ⇒ "52"
(number->string 42 10) ⇒ "42"
(number->string 42 16) ⇒ "2a"
(number->string 42 15) ⇒ "Unsupported base: 15"
(string->number numeric-string [base])
Converts numeric-string to an integer, in the given base. Allowed bases are 2, 8, 10, and 16. If the base is omitted, 10 is used. No base prefixes (e.g. 0x
for base 16) are allowed. Specifying an unsupported base will result in 0
.
(string->number "42") ⇒ 42
(string->number "101010" 2) ⇒ 42
(string->number "52" 8) ⇒ 42
(string->number "42" 10) ⇒ 42
(string->number "2a" 16) ⇒ 42
(string->number "42" 15) ⇒ 0
(str sexpr…)
Creates a string from concatenating the string forms of all the _sexpr_s.
(str 1) ⇒ "1"
(str 'symbol) ⇒ "symbol"
(str '(1 2 3)) ⇒ "(1 2 3)"
Comparisons
All comparison operations work with floating point numbers as well.
(< number number)
Returns whether the first argument is less than the second argument.
(< 1 2) ⇒ #t
(< 2 1) ⇒ #f
(< 2 2) ⇒ #f
(> number number)
Returns whether the first argument is greater than the second argument.
(> 1 2) ⇒ #f
(> 2 1) ⇒ #t
(> 2 2) ⇒ #f
(== number number)
(eq? sexpr sexpr)
(eqv? sexpr sexpr)
(equal? sexpr sexpr)
Returns whether the first argument is equal to the second argument.
(== 1 2) ⇒ #f
(== 1 1) ⇒ #t
(!= number number)
(neq? sexpr sexpr)
Returns whether the first argument is not equal to the second argument.
(!= 1 2) ⇒ #t
(!= 1 1) ⇒ #f
(<= number number)
Returns whether the first argument is less than or equal to the second argument.
(<= 1 2) ⇒ #t
(<= 2 1) ⇒ #f
(<= 2 2) ⇒ #t
(>= number number)
Returns whether the first argument is greater than or equal to the second argument.
(>= 1 2) ⇒ #f
(>= 2 1) ⇒ #t
(>= 2 2) ⇒ #t
(odd? number)
Returns whether the argument (which must be an integer) is odd.
(odd? 2) ⇒ #f
(odd? 5) ⇒ #t
(even? number)
Returns whether the argument (which must be an integer) is even.
(even? 2) ⇒ #t
(even? 5) ⇒ #f
(zero? number)
Returns whether the argument is zero.
(zero? 1) ⇒ #f
(zero? 0) ⇒ #t
(zero? -1) ⇒ #f
(positive? number)
Returns whether the argument is positive.
(positive? 1) ⇒ #t
(positive? 0) ⇒ #f
(positive? -1) ⇒ #f
(negative? number)
Returns whether the argument is negative.
(negative? 1) ⇒ #f
(negative? 0) ⇒ #f
(negative? -1) ⇒ #t
Logical
(not sexpr)
Returns whether the boolean negation of the argument.
(! #t) ⇒ #f
(! #f) ⇒ #t
(and sexpr…)
Computes a logical and of the evaluated arguments, with shortcircuiting. I.e. each argument is evaluated in order until one evaluates to false or the end of the list is reached. If all evaluate to true the result of the and
is true, otherwise it is result of evaluating the final argument. If an argument evaluates to false, subsequent arguments are not evaluated. and
with no arguments evaluates to true.
(or sexpr…)
Computes a logical or of the evaluated arguments, with shortcircuiting. I.e. each argument is evaluated in order until one evaluates to true or the end of the list is reached. If all evaluate to false the result of the or
is false, otherwise it is the result of evaluating the final argument. If an argument evaluates to true, subsequent arguments are not evaluated. or
with no arguments evaluates to false.
Binary
(binary-and int-1 int-2)
Performs a bitwise AND of int-1 and int-2, returning the result.
(number->string (binary-and 0xaa 0x0f) 16) ⇒ "a"
(number->string (binary-and 0xaa 0xf0) 16) ⇒ "a0"
(binary-or int-1 int-2)
Performs a bitwise OR of int-1 and int-2, returning the result.
(number->string (binary-or 0xaa 0x0f) 16) ⇒ "af"
(number->string (binary-or 0xaa 0xf0) 16) ⇒ "fa"
(binary-not int)
Performs a bitwise NOT of int, returning the result.
(number->string (binary-not 0x000000aa) 16) ⇒ "ffffff55"
(left-shift int count)
Shifts int left by count bits, returning the result.
(number->string (left-shift (string->number "10101010" 2) 1) 2) ⇒ "101010100"
(number->string (left-shift (string->number "10101010" 2) 3) 2) ⇒ "10101010000"
(right-shift int count)
Shifts int right by count bits, returning the result.
(number->string (right-shift (string->number "10000" 2) 1) 2) ⇒ "1000"
(number->string (right-shift (string->number "10000" 2) 4) 2) ⇒ "1"
Lists
(cons sexpr1 sexpr2)
Creates a pair from sexpr1
and sexpr2
. If sexpr2
is a list, then the effect of cons
is to return sexpr2
prefixed by sexpr1
.
(cons 1 2) ⇒ (1 . 2)
(cons 1 '(2 3)) ⇒ (1 2 3)
(list sexpr…)
Creates a list out of a sequence of values.
(list 1 2 3) ⇒ (1 2 3)
(list (+ 1 1) (+ 2 2) (+ 3 3)) ⇒ (2 4 6)
(list 'a 'b '(c d)) ⇒ (a b (c d))
(make-list k [sexpr])
Returns a newly allocated list of length k, whose elements are all sexpr. If elemen_sexpr_ is not supplied, it defaults to the empty list.
(make-list 4 ’c) ⇒ (c c c c)
(cons* sexpr…)
cons*
is similar to list
, except that cons*
conses together the last two arguments rather than consing the last argument with the empty list. If the last argument is not a list the result is an improper list. If the last argument is a list, the result is a list consisting of the initial arguments and all of the items in the final argument. If there is only one argument, the result is the argument.
(cons* 'a 'b 'c) ⇒ (a b . c)
(cons* 'a 'b '(c d)) ⇒ (a b c d) ; compare to list above
(cons* 'a) ⇒ a
(cons* '(a b)) ⇒ (a b)
(append sexpr…)
Successively append the result of evaluating each argument. This makes a copy of each argument, appending it to the resulting list. Arguments need not be lists.
(append '(1 2 3) '(4 5 6)) ⇒ (1 2 3 4 5 6)
(append '(1 2 3) 4) ⇒ (1 2 3 4)
(append 1 '(2 3 4) 5 6) ⇒ (1 2 3 4 5 6)
(append! list sexpr)
Append the result of evaluating sexpr
to list
. This appends to list
, and returns it. The side effect (indicated by the !
) is that list
is modified, unlike append
. For example:
(define a '(1 2 3))
(append a '(4 5 6)) ⇒ (1 2 3 4 5 6)
a ⇒ (1 2 3)
(append! a '(4 5 6)) ⇒ (1 2 3 4 5 6)
a ⇒ (1 2 3 4 5 6)
(length list)
Return the number of items in list
. Nested lists are a single thing.
(length '()) ⇒ 0
(length '(1 2 3)) ⇒ 3
(length '(1 (2 3) 4)) ⇒ 3
(interval hi)
Creates a list of numbers from 1 to hi
, inclusive. hi
must be a positive integer.
(interval 5) ⇒ (1 2 3 4 5)
(interval 2) ⇒ (1 2)
(interval lo hi)
Creates a list of numbers from lo
to hi
, inclusive, stepping by 1. If lo
> hi
, a step of -1 is used.
(interval 1 5) ⇒ (1 2 3 4 5)
(interval -2 2) ⇒ (-2 -1 0 1 2)
(interval 5 1) ⇒ (5 4 3 2 1)
(interval 2 -2) ⇒ (2 1 0 -1 -2)
(interval lo hi step)
Creates a list of numbers from lo
to hi
, inclusive (if possible),
step
apart. step
must be non-zero and it’s sign must match the ordering of lo
and hi
. I.e. if lo
> hi
, step
must be negative, otherwise positive.
(interval 1 5 2) ⇒ (1 3 5)
(interval 1 8 2) ⇒ (1 3 5 7)
(interval -2 2 2) ⇒ (-2 0 2)
(interval 2 -2 -2) ⇒ (2 0 -2)
(interval 5 1 -2) ⇒ (5 3 1)
(interval -1 -8 -2) ⇒ (-1 -3 -5 -7)
(car list)
(head list)
Returns the first item in the list, i.e. the car pointer of the first cell in the chain referenced by list.
(car nil) ⇒ nil
(car '(a)) ⇒ a
(car '(a b c)) ⇒ a
(car (list (+ 1 1) (+ 2 2))) ⇒ 2
(cdr list)
(rest list)
(tail list)
Returns the rest of the list, i.e. the cdr pointer of the first cell in the chain referenced by list. rest
is provided as an alias.
(cdr nil) ⇒ nil
(cdr '(a)) ⇒ nil
(cdr '(a b c)) ⇒ (b c)
(cdr (list (+ 1 1) (+ 2 2))) ⇒ (4)
The above car
and cdr
functions are the fundamental list decomposition operations. There are more that are based on these two. First there are a series of functions explicitly using car
and cdr
such as caddr
which is equivalent to (car (cdr (cdr list)))
. Up to cddddr
is supported.
(caddr '(1 2 3 4 5)) ⇒ 3
(cdddr '(1 2 3 4 5)) ⇒ (4 5)
(general-car-cdr list path)
This is a generalization of car and cdr. The argument path
encodes a particular sequence of car
and cdr
operations, which general-car-cdr
executes on list
. path
is an non-negative integer that encodes the operations in a bitwise fashion: a zero bit represents a cdr
operation, and a one bit represents a car
. The bits are executed LSB to MSB, and the most significant one bit, rather than being interpreted as an operation, signals the end of the sequence.
For example, the following are equivalent:
(general-car-cdr object #b1011)
(cdr (car (car object)))
Here is a partial table of path
/operation equivalents:
#b10 cdr
#b11 car
#b100 cddr
#b101 cdar
#b110 cadr
#b111 caar
#b1000 cdddr
(first list)
(second list)
(third list)
(fourth list)
(fifth list)
(sixth list)
(seventh list)
(eighth list)
(ninth list)
(tenth list)
These do exactly what you’d think: return the first, second, etc. item from list.
(nth list n)
Returns the nth element of list, the first being at n = 1.
(nth '(1 2 3 4) 1) ⇒ 1
(nth '(1 2 3 4) 3) ⇒ 3
(list-ref list k)
Similar to nth
, but is the proper Scheme implementation: Returns the kth element of list, the first being at k = 0.
(list-ref '(1 2 3 4) 0) ⇒ 1
(list-ref '(1 2 3 4) 3) ⇒ 4
(take k list)
Returns a newly allocated list consisting of the first k elements of list. K must not be greater than the length of list.
(take 3 '(1 2 3 4 5)) ⇒ '(1 2 3)
(drop k list)
Returns the sublist of list obtained by omitting the first k elements. The result, if it is not the empty list, shares structure with list. K must not be greater than the length of list.
(drop 3 '(1 2 3 4 5)) ⇒ '(4 5)
The following two functions are functionally identical to take
and drop
, respecively, but use the correct Scheme syntax and argument order.
(list-head list k)
Returns a newly allocated list consisting of the first k elements of list. K must not be greater than the length of list.
(list-head '(1 2 3 4 5) 3) ⇒ '(1 2 3)
(list-tail list k)
Returns the sublist of list obtained by omitting the first k elements. The result, if it is not the empty list, shares structure with list. K must not be greater than the length of list.
(list-tail '(1 2 3 4 5) 3) ⇒ '(4 5)
(sublist list start end)
Start and end must be integers satisfying
0 <= start <= end <= (length list)
sublist
returns a newly allocated list formed from the elements of list beginning at
index start (inclusive) and ending at end (exclusive).
(memq object list)
(memv object list)
(member object list)
Returns the first pair of list whose car is object; the returned pair is always one from which list is composed. If object does not occur in list, #f
(n.b.: not the empty list) is returned.
(memp predicate list)
Returns the first pair of list for which predicate returns #t
when passed the car; the returned pair is always one from which list is composed. If predicate never returns #t
, #f
(n.b.: not the empty list) is returned.
(find predicate list)
Returns the first element in list for which predicate is true; returns #f
if it doesn’t find such an element. Predicate must be a procedure of one argument.
(find even? '(3 1 4 1 5 9)) ⇒ 4
(find-tail predicate list)
Returns the first pair of list whose car
satisfies predicate; returns #f
if there’s no such pair.
(find-tail even? '(3 1 4 1 5 9)) ⇒ (4 1 5 9)
(map function list…)
Applies function (which has to take the same number of arguments as there are list arguments to map
) to the corresponding elements of each list argument in order, returning the list of the results.
(map - '(1 2 3 4))) ⇒ (-1 -2 -3 -4)
(map (lambda (x)
(* x x))
'(1 2 3 4)) ⇒ (1 4 9 16)
(map + '(1 2 3) '(4 5 6))) ⇒ (5 7 9)
(map list '(1 2 3) '(4 5 6) '(7 8 9)) ⇒ ((1 4 7) (2 5 8) (3 6 9))
The list argument do not need to be the same length, but map
will only iterate a number of times equal to the length of the shortest. So
(map list '(1 2) '(4 5 6) '(7 8 9 10)) ⇒ ((1 4 7) (2 5 8))
since the shortest list argument contains two elements. Thus map
iterates twice (over the first two sets of values) creating a result list with two elements in it.
(for-each function list…)
Applies function (which has to take the same number of arguments as there are list arguments to map
) to the corresponding elements of each list argument in order. In this it is identical to map
; the difference is that for-each
returns nil. This is handy when you want to map over a list (or lists) for the side effect of the operation on each element (or group of elements), without regard for the results.
(reduce-left function initial list)
(reduce function initial list)
Combines all the elements of list using the binary operation function. For example, using +
one can add up all the elements:
(reduce-left + 0 list-of-numbers)
The argument initial is used only if list is empty; in this case initial is the result of the call to reduce-left
. If list has a single item, that item is returned. Otherwise, the arguments are reduced in a left-associative fashion. For example:
(reduce-left + 0 '(1 2 3 4)) ⇒ 10
(reduce-left + 0 '(1 2)) ⇒ 3
(reduce-left + 0 '(1)) ⇒ 1
(reduce-left + 0 '()) ⇒ 0
(reduce-left list '() '(1 2 3 4)) ⇒ (((1 2) 3) 4)
reduce
is an alias of reduce-left
, kept in for legacy support.
(reduce-right function initial list)
Like reduce-left
except that it is right-associative.
(reduce-right list '() '(1 2 3 4)) ⇒ (1 (2 (3 4)))
(fold-right function initial list)
Combines all of the elements of list using the binary operation procedure. Unlike reduce-left
and reduce-right
, initial is always used:
(fold-right + 0 '(1 2 3 4)) ⇒ 10
(fold-right list '() '(1 2 3 4)) ⇒ (1 (2 (3 (4 ()))))
Fold-right has interesting properties because it establishes a homomorphism between (cons
, ()
) and (procedure, initial). It can be thought of as replacing the pairs in the spine of the list with procedure and replacing the ()
at the end with initial. Many of the classical list-processing procedures can be expressed in terms of fold-right
, at least for the simple versions that take a fixed number of arguments:
(define (copy-list list)
(fold-right cons '() list))
(define (append list1 list2)
(fold-right cons list2 list1))
(define (map p list)
(fold-right (lambda (x r) (cons (p x) r)) '() list))
(define (reverse items)
(fold-right (lambda (x r) (append r (list x))) '() items))
(fold-left function initial list)
Combines all the elements of list using the binary operation procedure. Elements are combined starting with initial and then the elements of list from left to right. Whereas fold-right
is recursive in nature, capturing the essence of cdr
-ing down a list and then computing a result, fold-left
is iterative in nature, combining the elements as the list is traversed.
(fold-left list '() '(1 2 3 4)) ⇒ ((((() 1) 2) 3) 4)
(define (length list)
(fold-left (lambda (sum element) (+ sum 1)) 0 list))
(define (reverse items)
(fold-left (lambda (x y) (cons y x)) () items))
(filter function list)
Returns a list containing only elements from the list
argument for
which function
returns true
.
(filter odd? '(1 2 3 4 5)) ⇒ (1 3 5)
(remove function list)
Returns a list containing only elements from the list
argument for
which function
returns false
.
(remove odd? '(1 2 3 4 5)) ⇒ (2 4)
(partition integer list)
Returns a list of lists. Each nested list in the result is integer
items long, except the final one, which may be shorter.
(partition 3 '(1 2 3 4 5 6 7 8)) ⇒ ((1 2 3) (4 5 6) (7 8))
(partition predicate list)
Returns a list of 2 lists. The first is all elements of list for which predicate returns #t
, and the second os the elements for which it returns #f
.
(partition even? '(1 2 3 4 5 6 7 8)) ⇒ ((2 4 6 8) (1 3 5 7))
(flatten list)
Returns a list with the contents of all top level nested lists placed directly in the result. This is best illustrated with some examples:
(flatten '(a b c d)) ⇒ (a b c d)
(flatten '(a (b c) d)) ⇒ (a b c d)
(flatten '(a (b (c d)))) ⇒ (a b (c d))
(flatten* list)
Returns a list with the contents of all nested lists placed directly in the result. This is also best illustrated with some examples:
(flatten* '(a b c d)) ⇒ (a b c d)
(flatten* '(a (b c) d)) ⇒ (a b c d)
(flatten* '(a (b (c d)))) ⇒ (a b c d)
(sort list predicate)
predicate
must be a procedure of two arguments that defines a total ordering on the elements of list
. In other words, if x
and y
are two distinct elements of ‘list’, then it must be the case that
(and (procedure x y) (procedure y x)) ⇒ #f
sort
returns a newly allocated list whose elements are those of list
, except that they are rearranged to be sorted in the order defined by predicate
. So, for example, if the elements of list
are numbers, and procedure is <
, then the resulting elements are sorted in monotonically nondecreasing order. Likewise, if procedure is >
, the resulting elements are sorted in monotonically nonincreasing order. To be precise, if ‘x’ and ‘y’ are any two adjacent elements in the result, where ‘x’ precedes ‘y’, it is the case that
(procedure y x) ⇒ #f
Set-like functions
(union list…)
Returns a list that contains all items in the argument list
s. Each item appears only once in the result regardless of whether it was repeated in any list
.
(union '(1 2 3) '(4 5)) ⇒ (1 2 3 4 5)
(union '(1 2 3) '(3 4 5)) ⇒ (1 2 3 4 5)
(union '(1 2 3 2) '(4 4 5)) ⇒ (1 2 3 4 5)
(intersection list…)
Returns a list that contains only items that are in all list
arguments.
(intersection '(1 2 3) '(3 4 5)) ⇒ (3)
(intersection '() '(3 4 5)) ⇒ ()
(complement list…)
Returns a list that contains only items that were in the first list
argument, but not in any of the subsequent argument list
s.
(complement '(1 2 3 4 5) '(1 3 5)) ⇒ (2 4)
(complement '() '(1 2)) ⇒ ()
(complement '(1 2 3 4 5) '(1 2) '(3 4)) ⇒ (5)
Association lists
Association lists (aka a-lists) are a classic Lisp way to handle associating a value with a key. Implementation is a list of dotted pairs: the car
is the key, and the cdr
is the value.
(acons key value [a-list])
This returns the result of adding a pair (key . value)
to a-list. key, value, and a-list are evaluated. If a-list is omitted, it defaults to nil
. The new pair is placed at the head of a-list
(if provided).
(acons 'a 1) ⇒ ((a . 1))
(acons 'a 1 '((b . 2) (c . 3))) ⇒ ((a . 1) (b . 2) (c . 3)))
(pairlis keys values [a-list])
Creates an a-list from lists of keys and values. A third argument can be provided which is an exicting a-list that the new pairs will be added to.
(pairlis '(a b) '(1 2)) ⇒ ((b . 2) (a . 1)))
(pairlis '(a b) '(1 2) '((c . 3) (d . 4))) ⇒ ((b . 2) (a . 1) (c . 3) (d . 4))))
Now that we have a way to create a-lists, we have a couple functions to get data out of them.
(assoc key a-list)
Return the pair from a-list whose car
is equal to key. The empty pair is returned is key isn’t found.
(assoc 'a '((a . 1) (b . 2) (c . 3))) ⇒ (a . 1)
(assoc 'c '((a . 1) (b . 2))) ⇒ ()
(rassoc value a-list)
Return the pair from a-list whose cdr
is equal to value. The empty pair is returned is value isn’t found.
(rassoc '1 '((a . 1) (b . 2) (c . 3))) ⇒ (a . 1)
(rassoc '3 '((a . 1) (b . 2))) ⇒ ()
(dissoc value a-list)
Remove the pair from a-list whose car
is equal to value. The resulting alist is returned.
(dissoc 'a '((a . 1) (b . 2))) ⇒ ((b . 2))
(dissoc 'b '((a . 1) (b . 2))) ⇒ ((a . 1))
Bytearrays
(list-to-bytearray list of bytes and/or bytearrays)
(list->bytearray list of bytes and/or bytearrays)
The list must be comprised of elements that are either numbers between 0 and 255, inclusive, or existing bytearray objects. The result is an object
containing a []byte
.
(list-to-bytearray '(1 2 3 4)) ⇒ [1 2 3 4]
(list-to-bytearray '(1 [2 3] 4)) ⇒ [1 2 3 4]
(list-to-bytearray '([1 2] [3 4])) ⇒ [1 2 3 4]
(bytearray-to-list bytearray)
(bytearray->list bytearray)
This is the opposite of the previous function. The result is a list containing the numbers in the bytearray.
(bytearray-to-list [1 2 3 4]) ⇒ (1 2 3 4)
(replace-byte bytearray index value)
Makes a copy of bytearray and replaces the byte at index with value. The new bytearray with the replaced byte is returned. index must be a valid index into the byte array (zero based), and value must be a valid byte value, i.e. between 0 and 255, inclusive.
(define a [1 2 3 4]) ⇒ [1 2 3 4]
(replace-byte a 2 100) ⇒ [1 2 100 4]
a ⇒ [1 2 3 4]
(replace-byte! bytearray index value)
Replaces the byte at index with value. index must be a valid index into the byte array (zero based), and value must be a valid byte value, i.e. between 0 and 255, inclusive. The original byte array is modified and the returned bytearray object is the one that is passed to the function.
(define a [1 2 3 4]) ⇒ [1 2 3 4]
(replace-byte! a 2 100) ⇒ [1 2 100 4]
a ⇒ [1 2 100 4]
(extract-byte bytearray index)
Fetch and return the byte at index. index must be a valid index into the byte array (zero based).
(extract-byte [1 2 3 4] 2) ⇒ 3
(append-bytes bytearray byte…)
(append-bytes bytearray list of bytes)
(append-bytes bytearray bytearray…)
Appends the rest of the arguments to a copy of the bytearray that is the first arg. The copy is returned. Things that can be appended are: a single byte, a sequence of bytes (as a sequence of separate arguments), a list of bytes, a bytearray object, a sequence of bytearray objects (as a sequence of separate arguments), and code that evaluates to a byte, list of bytes, or bytearray.
(append-bytes [1 2 3] 4) ⇒ [1 2 3 4]
(append-bytes [1 2 3] 4 5 6) ⇒ [1 2 3 4 5 6]
(append-bytes [1 2 3] '(4 5 6)) ⇒ [1 2 3 4 5 6]
(append-bytes [1 2 3] [4 5 6]) ⇒ [1 2 3 4 5 6]
(append-bytes [1 2 3] [4 5] [6]) ⇒ [1 2 3 4 5 6]
(append-bytes [1 2 3] (list 4 5 6)) ⇒ [1 2 3 4 5 6]
(append-bytes! bytearray byte…)
(append-bytes! bytearray list of bytes)
(append-bytes! bytearray bytearray…)
As with append-bytes
, but modifies and returns the bytearray that is passed in rather than making a copy.
(define a [1 2 3]) ⇒ [1 2 3]
(append-bytes a 4) ⇒ [1 2 3 4]
a ⇒ [1 2 3]
(append-bytes! a 4) ⇒ [1 2 3 4]
a ⇒ [1 2 3 4]
(take k bytearray)
As with the list implementation of take
, fetches and returns a new bytearray consisting of the bytes from bytearray starting at index k.
(take 1 [1 2 3] ⇒ [1])
(take 3 [1 2 3] ⇒ [1 2 3])
(drop k bytearray)
(drop 1 [1 2 3] ⇒ [2 3])
(drop 2 [1 2 3] ⇒ [3])
As with the list implementation of drop
, fetches and returns a new bytearray consisting of the bytes from bytearray prior to index k.
(extract-bytes bytearray index length)
Returns a new bytearray consisting of length bytes from bytearray, starting at index index. This is functionally equivalent to (take length (drop index bytearray)) with bounds checking added.
(extract-bytes [1 2 3 4 5] 0 1) ⇒ [1]
(extract-bytes [1 2 3 4 5] 0 3) ⇒ [1 2 3]
(extract-bytes [1 2 3 4 5] 2 1) ⇒ [3]
(extract-bytes [1 2 3 4 5] 2 3) ⇒ [3 4 5]
Strings
(string-split string separator)
Splits string
at each occurance of separator
, returning a list of substrings. The occurances of separator
are discarded.
(string-split "hello, world" ", ") ⇒ ("hello" "world")
(string-join list [separator])
Joins the elements of list
(which must be strings), separating them with separator
.
(string-join '("hello" "world") ", ") ⇒ "hello, world"
(string-trim string [chars])
Trims all characters in chars
(which is a string) from each end of string
and returns the resulting string. If chars
is omitted it defaults to whitespace characters.
(string-trim "+++hello-^-" "+-^") ⇒ "hello"
(string-trim " hello ") ⇒ "hello"
(string-trim-left string [chars])
Trims all characters in chars
(which is a string) from beginning of string
and returns the resulting string. If chars
is omitted it defaults to whitespace characters.
(string-trim-left "+++hello-^-" "+-^") ⇒ "hello-^-"
(string-trim-left " hello ") ⇒ "hello "
(string-trim-right string [chars])
Trims all characters in chars
(which is a string) from each end of string
and returns the resulting string. If chars
is omitted it defaults to whitespace characters.
(string-trim-right "+++hello-^-" "+-^") ⇒ "+++hello"
(string-trim-right " hello ") ⇒ " hello"
(string-downcase string)
(string-downcase! string)
string-downcase
returns a newly allocated copy of string
in which all uppercase letters are changed to lowercase. string-downcase!
changes the original string.
(define s "ABCDEFG") ⇒ "ABCDEFG"
(string-downcase s) ⇒ "abcdefg"
s ⇒ "ABCDEFG"
(string-downcase! s) ⇒ "abcdefg"
s ⇒ "abcdefg"
(string-upcase string)
(string-upcase! string)
string-upcase
returns a newly allocated copy of string
in which all lowercase letters are changed to uppercase. string-upcase!
changes the original string.
(define s "abcdefg") ⇒ "abcdefg"
(string-upcase s) ⇒ "ABCDEFG"
s ⇒ "abcdefg"
(string-upcase! s) ⇒ "ABCDEFG"
s ⇒ "ABCDEFG"
(string-capitalize string)
(string-capitalize! string)
string-upcase
returns a newly allocated copy of string
in which all lowercase letters are changed to uppercase. string-capitalize!
changes the original string.
(define s "abcdefg") ⇒ "abcdefg"
(string-capitalize s) ⇒ "Abcdefg"
s ⇒ "abcdefg"
(string-capitalize! s) ⇒ "Abcdefg"
s ⇒ "Abcdefg"
(substring string start end)
Returns a newly allocated string formed from the characters of strin_g beginning with index _start (inclusive) and ending with end (exclusive).
(substring "" 0 0) ⇒ ""
(substring "arduous" 2 5) ⇒ "duo"
(substring? pattern string)
Pattern must be a string. Searches string to see if it contains the substring pattern. Returns #t
if pattern is a substring of string, otherwise returns #f
.
(substring? "rat" "pirate") ⇒ #t
(substring? "rat" "outrage") ⇒ #f
(substring? "" any-string) ⇒ #t
(string-prefix? string1 string2)
Return #t
if the first string1 (substring) forms the prefix of the string2; otherwise returns #f
.
(string-prefix? "abc" "abcdef") ⇒ #t
(string-prefix? "" any-string) ⇒ #t
(string-prefix? "abc" "defghi") ⇒ #f
(string-suffix? string1 string2)
Return #t
if the first string1 (substring) forms the suffix of the string2; otherwise returns #f
.
(string-suffix? "def" "abcdef") ⇒ #t
(string-suffix? "" any-string) ⇒ #t
(string-suffix? "abc" "defghi") ⇒ #f
(string-length string)
Returns the length of string as an non-negative integer (accounts correctly for multi-byte characters).
(string-length "") ⇒ 0
(string-length "The length") ⇒ 10
(string-null? string)
Returns #t
if string has zero length; otherwise returns #f
.
(string-null? "") ⇒ #t
(string-null? "Hi") ⇒ #f
(string=? string1 string2)
(string-ci=? string1 string2)
Returns #t
if the two strings are the same length and contain the same characters in the same (relative) positions; otherwise returns #f. string-ci=? and doesn’t distinguish uppercase and lowercase letters, but string=? does.
(string=? "PIE" "PIE")
(string=? "PIE" "pie")
(string-ci=? "PIE" "pie")
(string<? string1 string2)
(string-ci<? string1 string2)
(string>? string1 string2)
(string-ci>? string1 string2)
(string=<? string1 string2)
(string-ci=<? string1 string2)
(string=>? string1 string2)
(string-ci=>? string1 string2)
These procedures compare strings according to the order of the characters they contain. The arguments are compared using a lexicographic (or dictionary) order. If two strings differ in length but are the same up to the length of the shorter string, the shorter string is considered to be less than the longer string.
Utility
(random-byte)
Returns a psuedo-random unsigned integer between 0 and 255, inclusive..
(random-byte) ⇒ 13
(random-byte) ⇒ 207
(sleep millis)
Sleep for millis milliseconds.
(sleep 1000) ;; resumes execution 1 second later
(time sexpr…)
Evaluates each sexpr and reports the time taken to do so.
(write-line sexpr)
Writes the concatenation of the string forms of _sexpr_s followed by a newline.
> (write-line "Hello, " 42 " world")
Hello, 42 world
==> ()
(str sexpr…)
If you provide multiple arguments to str
it creates a string from
concatenating the string forms of all the _sexpr_s.
(str 1 "." 2) ⇒ "1.2"
(intern string)
Creates a symbol from string. All symbols are interned, and thus have a sole instance for a given sequence of characters. This minimizes overhead and at the same time makes symbol comparison far more efficient.
(gensym)
(gensym prefix)
Created a new, unique symbol made from the supplied prefix (or GENSYM
if a prefix is not supplied) and an increasing integer. This is useful when you are generating code and need a unique name (in a macro, for example).
(gensym) ⇒ GENSYM-1
(gensym) ⇒ GENSYM-2
(gensym) ⇒ GENSYM-3
(gensym "hi") ⇒ hi-1
(gensym "ho") ⇒ ho-1
(gensym "hi") ⇒ hi-2
(gensym "ho") ⇒ ho-2
(gensym "ho") ⇒ ho-3
(gensym "hi") ⇒ hi-3
(gensym) ⇒ GENSYM-4
(gensym-naked)
(gensym-naked prefix)
Works the same as gensym
, only the unique symbol returned will be a naked symbol.
(gensym-naked) ⇒ GENSYM-1:
(gensym-naked) ⇒ GENSYM-2:
(gensym-naked) ⇒ GENSYM-3:
(gensym-naked "hi") ⇒ hi-1:
(gensym-naked "ho") ⇒ ho-1:
(gensym-naked "hi") ⇒ hi-2:
(gensym-naked "ho") ⇒ ho-2:
(gensym-naked "ho") ⇒ ho-3:
(gensym-naked "hi") ⇒ hi-3:
(gensym-naked) ⇒ GENSYM-4:
(copy sexpr)
Make a copy of the result of evaluating sexpr, IFF it’s mutable. This is limited to lists and association lists. All other values are immutable. Copying an immutable item will return the item, whereas copying a list or association list will make a deep copy of the structure, and return it.
(exec command arg…)
Makes an operating system call. command
is the command to execute and the arg
s are the arguments passed on the command line to command
. command
must be a string, and the arg
s can be anything.
Concurrency
GoLisp has limited concurrency support that is built on top of goroutines and channels.
These functions make use of a process object. This is an opaque piece of data that wraps a custom structure used by the concurrency code; it is returned from fork
and schedule
and is used by proc-sleep
, wake
, and abandon
to interact with the underlying goroutine.
(fork function)
Executes function in a separate goroutine. When function ends, the goroutine terminates. function takes a single argument which is the process object that is returned.
(define (run-once proc)
(write-line "start")
(sleep 1000)
(write-line "stop"))
(fork run-once)
> start
[a second goes by]
stop
[run-once completes and the goroutine terminates]
(proc-sleep process millis)
Use proc-sleep
in a forked function to sleep for millis milliseconds. Using proc-sleep
rather than sleep
(which can be used) allows code in another process (that has a reference to the process object of the forked code) to preemptively terminate the sleep using the wake
function.
proc-sleep
returns a boolean that indicates whether the sleep was terminated using wake
.
(wake process)
Preemptively terminate a proc-sleep
in the code associated with process.
> (define (run proc)
(do ((woken #f woken))
(woken (write-line "woken"))
(write-line "tick")
(set! woken (proc-sleep proc 10000))))
> (define p (fork run))
tick
tick
[times goes by, tick is printed every 10 seconds]
> (wake p)
woken
[run completes and the goroutine terminates]
(join process)
Blocks the calling function until the process completes or aborts with an error. The return value of the process is returned, or nil if the process ran into an error and aborted. Attempting to call join
on a process twice raises an error.
(define (run proc) '(1 2 3))
(define p (fork run))
(join p) ⇒ (1 2 3)
(schedule millis function)
Schedule function to be evaluated in a separate goroutine millis milliseconds from now. function takes a single argument which is the process object that is returned. The process object associated with that goroutine is returned immediately.
> (define (run-delayed proc)
(write-line "running"))
> (schedule 10000 run-delayed)
[10 seconds pass]
running
[run-delayed completes and the goroutine terminates]
(abandon process)
Cancels the scheduled evaluation associated with process.
> (define (run-delayed proc)
(write-line "running"))
> (define p (schedule 10000 run-delayed))
[5 seconds pass]
> (abandon p)
[the delay is cancelled and the goroutine terminates]
(reset-timeout process)
Resets the timer on a scheduled process. Causing it to start over. You can use this function to postpone the evaluation of scheduled code.
> (define (run-delayed proc)
(write-line "running"))
> (define p (schedule 10000 run-delayed))
[less than 10 seconds pass]
> (reset-timeout p)
[10 seconds pass]
running
[run-delayed completes and the goroutine terminates]
Atomic Operations
GoLisp has support for several kinds of atomic operations. These can be useful for protecting memory when working with GoLisp code with concurrent processes, or just Go code with multiple goroutines.
These functions make use of a atomic object. This is an opaque piece of data that wraps an integer; it is returned from atomic
and is used by all the atomic-*
primitives to interact with the underlying integer using only atomic operations.
(atomic [value])
Creates a new atomic object and returns it. It can optionally be passed a starting value to initialize to. Otherwise, the starting value is 0.
> (atomic) ⇒ <atomic object with value 0>
> (atomic 5) ⇒ <atomic object with value 5>
(atomic-load atomic)
Loads the current integer value of the atomic object and returns it as an integer.
> (define a (atomic 5))
> (atomic-load a) ⇒ 5
(atomic-store! atomic new)
Stores a new integer value in a atomic object.
> (define a (atomic 5))
> (atomic-store! a 8)
> (atomic-load a) ⇒ 8
(atomic-add! atomic delta)
Adds the delta value to the one stored in the atomic object. The new sum is also returned.
> (define a (atomic 5))
> (atomic-add! a 4) ⇒ 9
> (atomic-load a) ⇒ 9
(atomic-swap! atomic new)
Swaps the value currently in the atomic object with a new value. The old value is returned.
> (define a (atomic 5))
> (atomic-swap! a 4) ⇒ 5
> (atomic-load a) ⇒ 4
(atomic-compare-and-swap! atomic old new)
The value in the atomic object is compared to old. If the value matches, the value in the atomic object is swapped with the value in new and true is returned. Otherwise, the values are not swapped and false is returned.
> (define a (atomic 5))
> (atomic-compare-and-swap! a 5 4) ⇒ #t
> (atomic-load a) ⇒ 4
> (define b (atomic 5))
> (atomic-compare-and-swap! b 9 4) ⇒ #f
> (atomic-load b) ⇒ 5
Channels
Channels are the main way you communicate between goroutines in Go. GoLisp has full support of channels.
(make-channel [buffer-size])
Creates a new channel object with an optional buffer size. If buffer-size is omitted or 0, the channel is unbuffered.
(channel-write channel value)
(channel<- value)
Writes a value to a channel. If the channel is unbuffered or has a full buffer, this call locks until there either another process tries to read from the channel or room is made in the buffer.
(channel-read channel)
(<-channel)
Reads a value from a channel. If the channel is unbuffered or has no buffered data, this call locks until there is data in the channel. <-channel
returns two values. The first value is the data read from the channel. The second value is a boolean flag stating whether there is more data in the channel. If the channel is closed and there are no more items left in the buffer, a false flag is returned. Otherwise, a true flag is returned. If a flag of false is returned, the first value will also be nil.
> (define c (make-channel 1))
> (channel-write c 1)
> (c<- 1) ; alternate syntax for the previous line
> (channel-read c) ⇒ (1 #t)
> (<-c) ⇒ (1 #t) ; alternate syntax for the previous line
> (channel-read c) ; blocks until another process writes to c
(channel-try-write channel value)
Tries to write a value to a channel. If the channel is unbuffered with nobody waiting for a write or has a full buffer, it returns immediately a false value. Otherwise, it writes the value to the channel and returns a true value.
(define c (make-channel 1) (channel-try-write c 1) ⇒ #t
(define c (make-channel)) (channel-try-write c 1) ⇒ #f
(channel-try-read channel)
Tries to reads a value from a channel. This call returns three values. The first is whether data could be read or not. The second is the data that is read, or nil if none was. The last value is whether the channel has more data in it.
> (define c (make-channel 1))
> (c<- 1)
> (channel-try-read c) ⇒ (#t 1 #t)
> (channel-try-read c) ⇒ (#f () #t)
(close-channel channel)
Closes the specified channel. The channel’s buffered is cleared by any other goroutines trying to read from it then all other reads immediately return with the more flag set to false. Trying to write to a closed channel or trying to close a channel twice results in an error.
> (define c (make-channel 1))
> (c<- 1)
> (close-channel c)
> (<-c) ⇒ (1 #t)
> (<-c) ⇒ (() #f)
> (<-c) ⇒ (() #f) ; repeats on subsequent calls
> (channel-try-read c) ⇒ (#t () #f)
Environments
Scheme (and thus GoLisp) is lexically scoped. This is implemented by the creation of a lexical environment (aka symbol table) for each lexical scope:
- function/lambda/macro invocations, which holds parameters and any local definitions
let
structures, which hold thelet
bindingsdo
structures, which hold thedo
bindings
Functions and lambdas capture a reference to the environment in which they were defined, so they always have access to it’s bindings (that’s a closure, btw).
Each environment has a connection to it’s containing environment, and can override/hide bindings in outer scopes. When a symbol is evaluated, the most local environment is searched first. If a binding for the system isn’t found there, the containing environment is searched. This continues until a binding for the sybol is found or we go all the way to the global environment and still can’t find a binding.
Section 3.2 of [1] does a great job of explaining environments in Scheme, which is the basis for environments in GoLisp.
In Scheme, some environments are more important than others, mainly as they tend to be larger, long lived, and serve as the root of many other environments as a program runs. These are known as top level environments. Specifially, these are the global environment (the only environment that is contained by nothing), and any environments directly below it in the environment tree. The REPL runs in one such environment, which effectively sandboxes it, protecting the bindings in the global environment from corruption.
(environment? sexpr)
Returns #t
if sexpr is an environment; otherwise returns #f
.
(environment-has-parent? environment)
Returns #t
if environment has a parent environment; otherwise returns #f
.
(environment-parent environment)
Returns the parent environment of environment. It is an error if environment has no parent.
(environment-bound-names environment)
Returns a newly allocated list of the names (symbols) that are bound by environment. This does not include the names that are bound by the parent environment of environment. It does include names that are unassigned or keywords in environment.
(environment-macro-names environment)
Returns a newly allocated list of the names (symbols) that are bound to syntactic keywords in environment.
(environment-bindings environment)
Returns a newly allocated list of the bindings of environment; does not include the bindings of the parent environment. Each element of this list takes one of two forms: (symbol)
indicates that symbol is bound but unassigned, while (symbol object)
indicates that symbol is bound, and its value is object
.
(environment-reference-type environment symbol)
Returns a symbol describing the reference type of symbol in environment or one of its ancestor environments. The result is one of the following:
normal
means symbol is a variable binding with a normal value.unassigned
means symbol is a variable binding with no value.macro
means symbol is a keyword binding.unbound
means symbol has no associated binding.
(environment-bound? environment symbol)
Returns #t
if symbol is bound in environment or one of its ancestor environments; otherwise returns #f
. This is equivalent to
(not (eq? ’unbound
(environment-reference-type environment symbol)))
(environment-assigned? environment symbol)
Returns #t
if symbol is bound in environment or one of its ancestor environments, and has a normal value. Returns #f
if it is bound but unassigned. Signals an error if it is unbound or is bound to a keyword.
(environment-lookup environment symbol)
symbol must be bound to a normal value in environment or one of its ancestor environments. Returns the value to which it is bound. Signals an error if unbound, unassigned, or a keyword.
(environment-lookup-macro environment symbol)
If symbol is a keyword binding in environment or one of its ancestor environments, returns the value of the binding. Otherwise, returns #f
. Does not signal any errors other than argument-type errors.
(environment-assignable? environment symbol)
symbol must be bound in environment or one of its ancestor environments. Returns #t
if the binding may be modified by side effect.
(environment-assign! environment symbol value)
symbol must be bound in environment or one of its ancestor environments, and must be assignable. Modifies the binding to have value as its value, and returns an unspecified result.
(environment-definable? environment symbol)
Returns #t
if symbol is definable in environment, and #f
otherwise.
(environment-define environment symbol value)
Defines symbol to be bound to object in environment, and returns an unspecified value. Signals an error if symbol isn’t definable in environment.
(eval sexpr environment)
Evaluates sexpr in environment. You rarely need eval in ordinary programs; it is useful mostly for evaluating expressions that have been created “on the fly” by a program.
(system-global-environment)
The function system-global-environment
is returns the distinguished environment that’s the highest level ancestor of all other environments. It is the parent environment of all other top-level environments. Primitives, system procedures, and most syntactic keywords are bound in this environment.
(the-environment)
Returns the current environment. This form may only be evaluated in a top-level environment. An error is signalled if it appears elsewhere.
(procedure-environment procedure)
Returns the closing environment of procedure. Signals an error if procedure is a primitive procedure.
(make-top-level-environment [names [values]])
Returns a newly allocated top-level environment. extend-top-level-environment creates an environment that has parent environment, make-top-level-environment creates an environment that has parent system-global-environment, and make- root-top-level-environment creates an environment that has no parent.
The optional arguments names and values are used to specify initial bindings in the new environment. If specified, names must be a list of symbols, and values must be a list of objects. If only names is specified, each name in names will be bound in the environment, but unassigned. If names and values are both specified, they must be the same length, and each name in names will be bound to the corresponding value in values. If neither names nor values is specified, the environment will have no initial bindings.
Environments in GoLisp differ slightly from standard Scheme in that they have a name attached. For the various forms of let
and do
this is simply "let"
and "do"
, respectively. Not of much use, but then these are just a byproduct of having lexical scopes. What’s more useful is the higher level environments. This brings us to the real reason for adding environment support: game integration sandboxes. When we were writing the game integration functionallity for Engine3, we wanted each game’s event handling to live in a separate sandbox. This is implemented buy creating a new top level environment under the global environment. The problem here is that it’s off in it’s own world, separate from the repl. By naming environments (in this case by the name of the game), we can add a function to return an environment given it’s name. That allows us to peek inside the sandbox from the repl, examining and manipulating the bindings there. And so we added a function to let us do that:
(find-top-level-environment name)
Returns the top level environment with the given name.
Input/Output
(open-input-file filename)
Takes a filename referring to an existing file and returns an input port capable of delivering characters from the file.
(open-output-file filename [append?])
Takes a filename referring to an output file to be created and returns an output port capable of writing characters to a new file by that name.
If append? is given and not #f
, the file is opened in append mode. In this mode, the contents of the file are not overwritten; instead any characters written to the file are appended to the end of the existing contents. If the file does not exist, append mode creates the file and writes to it in the normal way.
(close-port port)
Closes port and returns an unspecified value. The associated file is also closed.
(write-bytes byte-array output-port)
Writes byte-array to output-port as a stream of raw bytes. Most usefull for interacting with external devices via serial/usb ports.
(write-string string [output-port])
Writes string to output-port, performs discretionary output flushing, and returns an unspecified value.
(newline [output-port])
Writes an end-of-line to output-port, performs discretionary output flushing, and returns an unspecified value.
(write sexpr [output-port])
Writes a written representation of sexpr to output-port, and returns an unspecified value. If sexpr has a standard external representation, then the written representation generated by write
shall be parsable by read
into an equivalent object. Thus strings that appear in the written representation are enclosed in doublequotes, and within those strings backslash and doublequote are escaped by backslashes. write
performs discretionary output flushing and returns an unspecified value.
(read [input-port])
Converts external representations of Scheme objects into the objects themselves. read returns the next object parsable from input-port, updating input-port to point to the first character past the end of the written representation of the object. If an end of file is encountered in the input before any characters are found that can begin an object, read returns an end-of-file object. The input-port remains open, and further attempts to read will also return an end-of-file object. If an end of file is encountered after the beginning of an object’s written representation, but the written representation is incomplete and therefore not parsable, an error is signalled.
(eof-object? sexpr)
Returns #t
if objec_sexpr_ is an end-of-file object; otherwise returns #f
.
(format destination control-string argument…)
Writes the characters of control-string to destination, except that a tilde (~
) introduces a format directive. The character after the tilde, possibly preceded by prefix parameters and modifiers, specifies what kind of formatting is desired. Some directives use an argument to create their output; the typical directive puts the next argument into the output, formatted in some special way. It is an error if no argument remains for a directive requiring an argument.
The output is sent to destination. If destination is #f
, a string is created that contains the output; this string is returned as the value of the call to format
. If destination is #t
, the output is sent to stdout
. In all other cases format
returns an unspecified value. Otherwise, destination must be an output port, and the output is sent there.
A format directive consists of a tilde (~), an optional prefix parameter, an optional at-sign (@
) modifier, and a single character indicating what kind of directive this is. The alphabetic case of the directive character is ignored. The prefix parameters are generally integers, notated as optionally signed decimal numbers.
In place of a prefix parameter to a directive, you can put the letter V
(or v
), which takes an argument for use as a parameter to the directive. Normally this should be an integer. This feature allows variable-width fields and the like. You can also use the character #
in place of a parameter; it represents the number of arguments remaining to be processed.
~A: The next argument, which may be any object, is printed as if by write-line. ~mincolA
inserts spaces on the right, if necessary, to make the width at least mincol columns. The @
modifier causes the spaces to be inserted on the left rather than the right.
~S: The next argument, which may be any object, is printed as if by write (in as read
able format as possible). ~mincolS
inserts spaces on the right, if necessary, to make the width at least mincol columns. The @
modifier causes the spaces to be inserted on the left rather than the right.
~%: This outputs a newline character.
This outputs a #\newline character. ~n%
outputs n newlines. No argument is used. Simply putting a newline in control-string would work, but ~%
is often used because it makes the control string look nicer in the middle of a program.
~~: This outputs a tilde. ~n~
outputs n tildes.
~newline: Tilde immediately followed by a newline ignores the newline and any following whitespace characters. With an @
, the newline is left in place, but any following whitespace is ignored. This directive is typically used when control-string is too long to fit nicely into one line of the program:
(define (type-clash-error procedure arg spec actual)
(format
#t
"~%Procedure ~S~%requires its %A argument ~
to be of type ~S,~%but it was called with ~
an argument of type ~S.~%"
procedure arg spec actual))
(type-clash-error ’vector-ref
"first"
'integer
'vector)
prints
Procedure vector-ref
requires its first argument to be of type integer,
but it was called with an argument of type vector.
Note that in this example newlines appear in the output only as specified by the ~% directives; the actual newline characters in the control string are suppressed because each is preceded by a tilde.
Frames
GoLisp contains a frame system inspired by Self [4] and NewtonScript [5].
A frame is a set of named slots that hold arbitrary values. Slot names must be symbols that end with a colon. For example: color:
, or height:
. When evaluated normally, these special symbols don’t get looked up in the environment, they simply evaluate to themselves.
(define a a:)
'a ⇒ a
a ⇒ a:
'a: ⇒ a:
a: ⇒ a:
Basic functions
(make-frame slot-name slot-value … )
Frames can be created using the make-frame
function, passing it an alternating sequence of slot names and values:
(make-frame a: 1 b: 2)
This results in a frame with two slots, named a:
and b:
with values 1
and 2
, respectively.
{ slot-name slot-value … }
This is an alternative syntax for defining frames:
{a: 1 b: 2}
Both are equivalent. Slot names and values in both cases are evaluated (this is one reason for the non-evaluating symbols: it avoiding having to quote literal slot names).
(clone frame)
Frames represent things. For example, you could use a frame that looks like {x: 1 y: 10}
to represent a point. A system that would use point frames will typically need many independant points. The approach to this is to create a prototypical point data frame, and use the clone
function to create individual, independant frames:
(define point {x: 1 y: 1})
(define p1 (clone point))
(set-slot! p1 x: 5)
(get-slot p1 x:) ⇒ 5
(get-slot point x:) ⇒ 1
(has-slot? frame slot-name)
(slot-name? frame)
The has-slot?
function is used to query whether a frame contains (directly or in an ancestor) the particular slot:
(define f {a: 1 b: 2})
(has-slot? f a:) ⇒ #t
(a:? f) ⇒ #t
(has-slot? f c:) ⇒ #f
(c:? f) ⇒ #f
(get-slot frame slot-name)
(slot-name frame)
The get-slot
function is used to retrieve values from frame slots:
(define f {a: 1 b: 2})
(get-slot f a:) ⇒ 1
(a: f) ⇒ 1
(get-slot f b:) ⇒ 2
(b: f) ⇒ 2
If the frame passed to get-slot
contains a slot with the specified name, it’s value is returned. If not, then parent frames are searched in a nondeterministic order until a slot with the specified name is found. If a matching slot is found, it’s value is returned. If none is found an error is raised.
(define f {a: 1 b: 2})
(define g {parent*: f c: 3})
(get-slot g c:) ⇒ 3
(get-slot g a:) ⇒ 1 ; from the frame f
(get-slot-or-nil frame slot-name)
The same as above, except that if a matching slot is not found, nil
is returned instead of raising an error.
(set-slot! frame slot-name new-value)
(slot-name! frame new-value)
The set-slot!
function is used to change values in frame slots:
(define f {a: 1 b: 2})
(get-slot f a:) ⇒ 1
(set-slot! f a: 5) ⇒ 5
(a:! f 5) ⇒ 5
(get-slot f a:) ⇒ 5
Trying to set a slot that doesn’t exist in the frame will result in a corresponding slot being created.
(define f {a: 1 b: 2})
(set-slot! f c: 5) ⇒ 5
f ⇒ {a: 1 b: 2 c: 5}
(remove-slot! frame slot-name)
The remove-slot!
function is used to function is used to remove a slot from a frame. It only removes slots from the frame itself. not any of it’s parents. remove-slot!
return #t
if the slot was removed, #f
otherwise.
(define f {a: 1 b: 2})
(remove-slot! f a:) ⇒ #t
f ⇒ {b: 2}
(remove-slot! f a:) ⇒ #f
(frame-keys frame)
Returns a list of the slot names in frame. Note that the order of the result is nondeterministic.
(frame-keys {a: 1 b: 2}) ⇒ (a: b:)
(frame-values frame)
Returns a list of the slot values in frame. Note that the order of the result is nondeterministic.
(frame-values {a: 1 b: 2}) ⇒ (1 2)
Parent slots
Frames can have slots that refer to other slots to provide prototype inheritance. These slots have names that have a *
immediately preceeding the trailing :
, for example proto*:
. The names of the parent slots don’t matter; it is the trailing *:
in the name that marks them as parent slots. A frame can have any number of parent slots.
When a slot is being searched for, if it isn’t found in the specified slot, these parent slots are recursively searched until the requested slot is found or the entire graph has been examined.
> (define y {a: 1})
==> {a: 1}
> (define x {b: 2 p*: y})
==> {b: 2 p*: {...}}
> (a: x)
==> 1
Note: Parent slots are searched in arbitrary order.
Function slots
Now things get interesting. Slot values can be functions (typically lambda
expressions) as well as data. Function slots can be executed by using the send
function
(send frame slot-name arg…)
(slot-name> frame arg…)
(define f {add: (lambda () (+ 1 2))})
(send f add:) ⇒ 3
(add:> f) ⇒ 3
As expected, parameters are supported:
(define f {add: (lambda (x) (+ 1 x))})
(send f add: 2) ⇒ 3
In the body of a function, slots can be refrenced like normal variables. To do so, simply omit the trailing colon:
(define f {a: 3
add: (lambda (x) (+ a x))})
(send f add: 2) ⇒ 5
Likewise, functions in the frame (or parent frames) can be referred to directly by name.
(define f {a: 5
b: 2
foo: (lambda (x) (+ x a))
bar: (lambda () (foo b))})
(send f bar:) ⇒ 7
Bindings defined in the local environment (e.g. by a let
form) hide frame slots of the same name. In the following, let
overrides the a:
slot by introducing a local binding for a
.
(let ((f {a: 42})
(g {parent*: f foo: (lambda ()
(let ((a 10))
(+ 1 a)))}))
(send g foo:) ⇒ 11
Of course the end game of all this is to be able to inherit functions from parent frames:
(define f {a: 5
foo: (lambda (x) (+ x a))})
(define g {parent*: f
b: 2
bar: (lambda () (foo b))})
(send g bar:) ⇒ 7
Notice that we’ve been saying parent frames, i.e. plural. Also note that parent slot names are arbitrary and for documentation purposes only. A frame can have any number of parents. When a slot is looked for, the explictily specified frame is searched first, recursively followed by parent frames in a nondeterministic order until a matching slot is found. If none are found, the result is nil.
(define e {a: 5})
(define f {b: 2})
(define g {parent-e*: e
parent-f*: f
foo: (lambda (x) (+ x a))
bar: (lambda () (foo b))}))
(send g bar:) ⇒ 7
(set-slot! g a: 10)
(get-slot g a:) ⇒ 10
When you set a slot with parent frames involved, if the slot is found in the explicit frame it’s value is set and the new value is returned. If it doesn’t exist in the explicit frame or any parent frame, it gets created in the explicit frame and the value returned. Otherwise the parents are searched until the slot is found and it’s value is set and returned.
(send-super slot-name arg…)
(slot-name^ arg…)
Like send
, but sends to the first parent that has the named slot. send-super
can only be used from within a function slot.
(apply-slot frame slot-name sexpr…)
Apply the function that results from evaluating the function in slot slot-name of frame to the argument list resulting from evaluating each sexpr.
Each initial sexpr can evaluate to any type of object, but the final one (and there must be at least one sexpr) must evaluate to a list.
(define f {foo: (lambda (x y z) (+ 1 x y z))})
(apply-slot f foo: 2 '(3 4)) ⇒ 10
(apply-slot f foo: '(2 3 4)) ⇒ 10
(apply-slot-super slot-name sexpr…)
Like apply-slot
, but sends to the first parent that has the named slot. apply-slot-super
can only be used from within a function slot.
Dynamic inheritence
Parent slots are slots like any other and can have their values changed at any time. This ability is somewhat unusual fort those with a heavy OO background but can be very useful for changing behavior on the fly. A prime example of this is the implimentation of a state machine. The functions for each state can be placed in different frames and transitions can modify the slot contaiing that state behavior.
Here’s an example of this.
(define state {name: ""
enter: (lambda ())
halt: (lambda ())
set-speed: (lambda (s))
halt: (lambda ())
transition-to: (lambda (s)
(set! state* s)
(enter))})
(define stop-state {name: "stop"
parent*: state
enter: (lambda ()
(set! speed 0)
(transition-to idle-state))})
(define idle-state {name: "idle"
parent*: state
set-speed: (lambda (s)
(set! speed s)
(transition-to start-state))})
(define start-state {name: "start"
parent*: state
halt: (lambda ()
(transition-tostop-state))
set-speed: (lambda (s)
(set! speed s)
(transition-to change-speed-state))})
(define change-speed-state {name: "change-speed"
parent*: state
halt: (lambda ()
(transition-to stop-state))
set-speed: (lambda (s)
(set! speed s))})
(define motor {speed: 0
state*: state
start: (lambda () (transition-to stop-state)) })
Now you can do things like the following:
(send motor start:)
motor ⇒ {speed: 0 state*: {name: "idle" ...}}
(send motor set-speed: 10)
motor ⇒ {speed: 10 state*: {name: "start" ...}}
(send motor set-speed: 20)
motor ⇒ {speed: 20 state*: {name: "change-speed" ...}}
(send motor set-speed: 15)
motor ⇒ {speed: 15 state*: {name: "change-speed" ...}}
(send motor halt:)
motor ⇒ {speed: 0 state*: {name: "idle" ...}}
Json support
GoLisp has built-in support for converting between stringified Json and frames.
(json->lisp string)
(json->lisp "{'key': [1, 2, 3]}") ⇒ {key: (1 2 3)}
(lisp->json frame)
(lisp->json {key: (1 2 3)}) ⇒ "{'key': [1, 2, 3]}"
Testing
Golisp has a builtin testing framework, completely written in GoLisp.
(context tag setup sexpr…)
The macro context
takes a name (usually a string, but it can be a plain symbol) as it’s first argument. This shoudl describe what the context focusses on. Next is a list of sexprs that are executed before each of the following sexprs. These sexprs should be ‘it’ expressions, each of which focus on a specific aspect of the context.
(it tag assertion…)
This defines a cohesive block of assertions. The context’s setup code will be run in a new environment for each it block.
(assert-true )
Passes if sexpr evaluates to a truthy value, fails otherwise.
(assert-false )
Passes if sexpr evaluates to a falsy value, fails otherwise.
(assert-eq )
Passes if the result of evaluating actual is equal to the result of evaluating expected, fails otherwise.
(assert-neq )
Passes if the result of evaluating actual is not equal to the result of evaluating expected, fails otherwise.
(assert-nil )
Passes is sexpr evaluates to nil, fails otherwise.
(assert-not-nil )
Passes is sexpr evaluates to anything other than nil, fails otherwise.
(assert-error )
Passes if evaluating sexpr results in an error being signalled, passes if it evaluates without problems.
Generally you should create a test file for each feature you are testing. The file is a plain lisp file and can contain any lisp code, including global variable and function definitions.
For example, here is the test file for scoping:
(context "environments"
((define a 5)
(define (foo a)
(lambda (x)
(+ a x))))
(it "can access a in the global env"
(assert-eq a 5))
(it "gets a from the function's local env"
(assert-eq ((foo 1) 5) 6)
(assert-eq ((foo 2) 5) 7)
(assert-eq ((foo 10) 7) 17)))
Running a test results in a stream of status output for each test, followed at the very end by a summary. Running the above results in the following:
environments
can access a in the global env
(assert-eq a 5)
gets a from the function's local env
(assert-eq ((foo 1) 5) 6)
(assert-eq ((foo 2) 5) 7)
(assert-eq ((foo 10) 7) 17)
Ran 4 tests in 0.003 seconds
4 passes, 0 failures, 0 errors
If we introduce a failure, the output would be:
environments
can access a in the global env
(assert-eq a 5)
gets a from the function's local env
(assert-eq ((foo 1) 5) 6)
(assert-eq ((foo 2) 5) 8)
- expected 8, but was 7
(assert-eq ((foo 10) 7) 17)
Ran 4 tests in 0.002 seconds
3 passes, 1 failures, 0 errors
Failures:
environments gets a from the function's local env:
(assert-eq ((foo 2) 5) 8)
- expected 8, but was 7
Errors are also reported. Errors are problems that occur while evaluating the clauses, that aren’t failures. Essentially they indicate bugs of some sort.
environments
can access a in the global env
(assert-eq a 5)
gets a from the function's local env
(assert-eq ((foo 1) 5) 6)
ERROR: Quotent: (7 0) -> Divide by zero.
Ran 3 tests in 0.002 seconds
2 passes, 0 failures, 1 errors
Errors:
environments gets a from the function's local env:
ERROR: Quotent: (7 0) -> Divide by zero.
The above output was generated by the testing framwork running in verbose mode. You can also run in quiet mode which only outputs the summary:
Ran 4 tests in 0.003 seconds
4 passes, 0 failures, 0 errors
You run tests by running the golisp repl in test mode, providing either a directory or filename. If you provide a directory all files in it that match *_test.lsp
will be run. If you provide a filename, only that file will be run.
$golisp -t tests/scope_test.lsp
Ran 4 tests in 0.002 seconds
4 passes, 0 failures, 0 errors
$golisp -t tests
Ran 935 tests in 0.273 seconds
935 passes, 0 failures, 0 errors
Adding the -v
flag will produce the detailed output above.
Defining primitives
The Go function MakePrimitiveFunction
allows you to create primitive functions.
MakePrimitiveFunction(name string, argCount string,
function func(*Data, *SymbolTableFrame)(*Data, error))
The arguments are:
-
The function name. This is the name of a symbol which will be used to reference the function.
- An argument count expectation. This is a string that specifies how many arguments the primitive expects. It can take several forms:
- A single, specific number. E.g. exactly two:
"2"
- A minimum number. E.g. at least two:
">=2"
- A range of values. E.g. between two and five, inclusive:
"(2,5)"
- One of a selection of the above: E.g.
"2|3|>=5"
- An unspecified number, any checking must be done in the primitive definition:
"*"
- A single, specific number. E.g. exactly two:
- The Go function which implements the primitive. This function must have the signature
func <Name>(*Data, *SymbolTableFrame) (*Data, error)
The implementing function takes two parameters as seen above:
-
A Lisp list containing the arguments
-
The environment in which the primitive is being evaluated. This is used when calling
Eval
orApply
.
Primitives use, like functions defined in LISP, applicative evaluation order. That means that all arguments are evaluated and the resulting values passed to the function. This frees you from having to evaluate the arguments and handle errors. You still have to verify the number of arguments (only if you used -1 as trhe argument cound in the MakePrimitiveFunction
call) and their type, if aplicable.
An example:
MakePrimitiveFunction("!", "1", BooleanNot)
func BooleanNot(args *Data, env *SymbolTableFrame) (result *Data, err error) {
val := BooleanValue(Car(args))
return BooleanWithValue(!val), nil
}
You can extend the goLisp runtime without changing any of it’s code. You simply import the golisp package (typically aliased to .
to make the code less noisy) and place calls to MakePrimitiveFunction
in your package’s init
block.
There is another, very similar function that you will typically not need unless you are hackign on the language itself (as opposed to adding builting functions):
MakeSpecialForm(name string, argCount int,
function func(*Data, *SymbolTableFrame) (*Data, error))
Arguments and the signature of the implementing function are identical to MakePrimitiveFunction
. The only difference is that this defines a special form which uses normal evaluation order. I.e. arguments are not evaluated before calling the function; the raw sexpressions are passed in. Thus the implementing function has full control over what gets evaluated and when.
MakeSpecialForm("if", "2|3", IfImpl)
func IfImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
c, err := Eval(Car(args), env)
if err != nil {
return
}
if BooleanValue(c) {
return Eval(Cadr(args), env)
} else {
return Eval(Caddr(args), env)
}
}
Data
The core lisp data element is the data type which logically contains a type tag and a value. The type tags are defined by the constants: ConsCellType
, NumberType
, BooleanType
, StringType
, SymbolType
, FunctionType
, PrimitiveType
, ObjectType
.
The types are described earlier. If you need to check the type of a piece of data you can fetch it’s type using the TypeOf(*Data) int
function and then compare it to a type tag constant. Additionally there are predicate functions for the most common types:
StringP(*Data) bool
returns whether the data is a string
SymbolP(*Data) bool
returns whether the data is a symbol
NumberP(*Data) bool
returns whether the data is a number
IntegerP(*Data) bool
returns whether the data is an integer
FloatP(*Data) bool
returns whether the data is a float
PairP(*Data) bool
returns whether the data is a cons cell (aka pair)
ListP(*Data) bool
is an alias for PairP
ObjectP(*Data) bool
returns whether the data is an encapsulated Go object
FunctionP(*Data) bool
returns whether the data is either a user defined or primitive function
Two other very handy functions are:
NilP(*Data) bool
returns whether the data is nil
NotNilP(*Data) bool
returns whether the data is non-nil
Creating and accessing data
There are various convenience functions that you can use to create data:
Cons(car *Data, cdr *Data) *Data
creates a cons cell with the provided values for it’s car
and cdr
.
IntegerWithValue(n int64) *Data
creates a number (integer) with the provided value
FloatWithValue(n float32) *Data
creates a number (float) with the provided value
BooleanWithValue(b bool) *Data
creates a boolean with the provided value
StringWithValue(s string) *Data
creates a string with the provided value
SymbolWithName(s string) *Data
creates a symbol with the provided name
ObjectWithTypeAndValue(typeName string, o unsafe.Pointer) *Data
creates an encapsulated Go object with the given type tag and value
Similarly there are functions for extracting information from data elements:
IntegerValue(*Data) int64
extracts the primitive integer value
FloatValue(*Data) float32
extracts the primitive float value
StringValue(*Data) string
extracts the primitive string value
BooleanValue(*Data) bool
extracts the primitive boolean value
TypeOfObject(*Data) string
extracts the type tag of the encapsulated Go object
ObjectValue(*Data) unsafe.Pointer
extracts the primitive object value
String(*Data) string
returns a human readable string representation of the data
Length(*Data) int
returns the length of a piece of Lisp data. This will be 0 in all cases except lists, when it will be the number of elements in the list.
IsEqual(*Data, o *Data) bool
compares two expressions:
-
if the two pointers point to the same thing, they are equal
-
nil is never equal to anything
-
data of different types are never equal
-
if the data are lists, they are equal if corresponding pairs of elements are equal
-
if values are the same (e.g. strings & numbers) they are equal
Any of the above ...Value
functions return the corresponding zero value if the data is not of the appropriate type.
Working with lists
Car(*Data) *Data
return the car
pointer from a cons cell (nil otherwise)
Cdr(*Data) *Data)
return the cdr
pointer from a cons cell (nil otherwise)
Caar
through Cddddr
returns a piece of the list as expected
First(*Data) *Data
Return the first item in a list (equivalent to car
)
…
Fifth(*Data) *Data
return the fifth item in a list
Nth(*Data, int) *Data
return the nth item in a list (starting at 1)
ArrayToList(sexprs []*Data) *Data
converts an array of Lisp data objects to a Lisp list.
Evaluation
These functions are the entry points into code evaluation. The main reason to use these, and especially Eval
, is to evaluate arguments to primitives as required.
Eval(d *Data, env *SymbolTableFrame) (*Data, error)
evaluates the expression in d
in the provided environment and returns the result along with any error that occurred during evaluation. If an error is returned, the value of the data result is indeterminate.
Apply(function *Data, args *Data, env *SymbolTableFrame) (*Data, error)
takes a function element, either user defined or primitive, and applies it to the provided arguments in the provided environment. This is actually how Eval
deals with a list: the evaluation of the car
of the list is the function, the cdr
is the list of arguments. It’s up to the primitive implementation to evaluate arguments as required.
The implementation of if
serves as an example:
func IfImpl(args *Data, env *SymbolTableFrame) (result *Data, err error) {
c, err := Eval(Car(args), env)
if err != nil {
return
}
if BooleanValue(c) {
return Eval(Cadr(args), env)
} else {
return Eval(Caddr(args), env)
}
}
Note that Cadr
and Caddr
return nil
if the corresponding element is missing from the argument list. Also, passing a nil
to Eval
returns nil
.
Embedded GoLisp
Now that you know what’s supported in GoLisp, and how to manipulate it’s structures and code in from Go, you need to get code into it. There are two ways to do this.
Single expression strings
If you have a Go string
that contains a single sexpr, you can pass it to Parse
which returns the list containing the parsed string and an error value. The returned structure can be passed to Eval
to evaluate it. The resulting sexpr is returned.
For example, here is the REPL:
prompt := "> "
LoadHistoryFromFile(".golisp_history")
lastInput := ""
for true {
input := *ReadLine(&prompt)
if input != "" {
if input != lastInput {
AddHistory(input)
}
lastInput = input
code, err := Parse(input)
if err != nil {
fmt.Printf("Error: %s\n", err)
} else {
d, err := Eval(code, Global)
if err != nil {
fmt.Printf("Error in evaluation: %s\n", err)
} else {
fmt.Printf("⇒ %s\n", String(d))
}
}
}
}
Loading a file
If you have GoLisp code in a plain text file you can pass the filename to ProcessFile
. This will parse and evaluate each sexpr contained in the file. The result of ProcessFile
is the value of the evaluation of the final sexpr in the file and an error value.
Generally, you load a file for it’s side effects: defining values and functions.
The REPL
You typically import the golisp
package into your Go app and use it. However, you can run the golisp/main/golisp
package, which puts you into the REPL where you can enter and evaluate one line at a time. You can use the load
function to load/evaluate files of golisp code you have written. If you provide a list of filenames on the command line they will be loaded and evaluated before dropping you into the REPL.
Note that you can easily make the REPL available to the user from within your application.
References
[1] Harold Abelson, Gerald Jay Sussman, and Julie Sussman. Structure and Interpretation of Computer Programs. MIT Press, Cambridge, Mass., 1985.
[2] Richard P. Gabriel and Kent M. Pitman. Endpaper: Technical issues of separation in function cells and value cells. Lisp and Symbolic Computation, 1(1):81–101, June 1988.
[3] Guy L. Steele. COMMON LISP: the language. Digital Press, 12 Crosby Drive, Bedford, MA 01730, USA, 1984. With contributions by Scott E. Fahlman and Richard P. Gabriel and David A. Moon and Daniel L. Weinreb.
[4] David Ungar and Randall B. Smith. self: the power of simplicity. In OOPSLA 87 Conference Proceedings, pages 227–241, Orlando, Florida, October 1987.
[5] Apple Computer. The NewtonScript Programming Language. Apple Computer, Cupertino, Ca., 1996.