Functions | |
| muse_cell | fn_quote (muse_env *env, void *context, muse_cell args) |
| Quotes the given arguments without evaluating them. | |
| muse_cell | fn_cons (muse_env *env, void *context, muse_cell args) |
| (cons head tail). | |
| muse_cell | syntax_lambda (muse_env *env, void *context, muse_cell args) |
| (fn formal-args <body>). | |
| muse_cell | syntax_block (muse_env *env, void *context, muse_cell args) |
| (fn: (arg1 arg2 --- argN) ---body---). | |
| muse_cell | syntax_let (muse_env *env, void *context, muse_cell args) |
| (let <variable-bindings> <body>). | |
| muse_cell | syntax_case (muse_env *env, void *context, muse_cell args) |
| (case object <match-cases>). | |
| muse_cell | fn_apply (muse_env *env, void *context, muse_cell args) |
| (apply fn arglist). | |
| muse_cell | fn_eval (muse_env *env, void *context, muse_cell args) |
| (eval s-expr). | |
| muse_cell | fn_callcc (muse_env *env, void *context, muse_cell args) |
| (call/cc (fn (k) --- (k result) ---)). | |
Quotes the given arguments without evaluating them.
For example,
(quote . hello)
'hello
(cons head tail).
Creates a new cons cell with the given head and tail. If no free cells are available, invokes the garbage collector and grows the heap if necessary.
(fn formal-args <body>).
Common syntax -
(fn (x1 x2 ... xN) expr1 expr2 ... result-expr)
fn creates a closure when it is executed. A closure is a copy of the body of the lambda with all non-parameter variables bound to their current values. If an undefined symbol (free variable) is used in the body, it will simply evaluate to itself. Once the symbol is defined after the creation of the closure, the defined value will take the place of the symbol in the closure.
For example -
(define norm3 (fn (x y z) (sqrt (+ (* x x) (* y y) (* z z)))))
norm3 to be a function that computes the norm (i.e. vector-length) of a 3-vector. You use the function in expressions like the following - (norm3 3 4 0) (norm3 10 20 30)
Binding formals -
Argments to a function are given as a list. Therefore a function may accept a variable number of arguments, computing different (but hopefully related) values in the different cases. The mechanism using which a function accesses its entire argument list is more general than purely for that case, though.
The arguments to the norm3 function in the above examples may be considered to be lists like
(10 20 30)
(x y z)
x, y and z in the formals list and the values in the arguments list. This is how the matching and binding is done.
In order to illustrate the matching method, let us write the norm3 argument list in its canonical form - as
(10 . (20 . (30 . ())))
(x . (y . (z . ())))
args), it will be bound to the entire argument list. If you use a pattern like(x . xs)
x will be bound to the first argument 10 and xs will be bound to the first cons cell's tail, i.e. the list(20 30)
(), but it is not valid to deconstruct a () value and assign its components to symbols. i.e. () will not match against (x . xs) but will match against xs.
(fn: (arg1 arg2 --- argN) ---body---).
Very similar to syntax_lambda, except that it doesn't create a closure. All the free variables in the block (i.e. those except the given arguments) are expected to be bound by the environment in which the block is invoked. Otherwise a block is identical to syntax_lambda and can be used in all places a closure can be used.
In particular, a block is effective with call/cc to specify jump out points such as exceptions and loop breaks. A normal closure can be used as well, but the closure will be repeatedly created every time the call/cc expression is evaluated.
Creating a block is extremely cheap compared to creating a closure using syntax_lambda. As a thumb rule, blocks are equivalent to closures when they are consumed within the scope of their declaration - as in the following example -
(map (fn: (x) (* x 2)) '(1 2 3 4 5))
(2 4 6 8 10)
fn: in this case is equivalent to using fn.If you define a symbol to a block, then you need to be careful, because the value of any free variables (non-local) used within the block can be redefined outside the block. For example -
(define y 2) (define double (fn: (x) (* x y))) (double 3) -> 6 (double 5) -> 10 (define y 3) (double 3) -> 9 (define y 100) (double 3) -> 300
(define y 2) (define double (fn (x) (* x y))) (double 3) -> 6 (double 5) -> 10 (define y 3) (double 3) -> 6 (define y 100) (double 3) -> 6
fn "captures" the value of y at the time it is invoked to create a closure. It is the closure thus created which is bound to the symbol "double".
(let <variable-bindings> <body>).
Syntax -
(let ((pattern1 value1)
(pattern2 value2)
...
(patternN valueN))
expr1
expr2
...
result-expr)
let introduces local variables bound to the results of given expressions in the context of a block of code. For example, an expression to compute the distance between two points (x1,y1) and (x2,y2) can be written as -
(let ((dx (- x2 x1))
(dy (- y2 y1)))
(print "Computing distance between two points ...")
(sqrt (* dx dx) (* dy dy)))
dx and dy bound to x2-x1 and y2-y1 respectively, in the expression (sqrt (* dx dx) (* dy dy)).
The result of the let expression is the result of the last expression in the body. In the above example, the last statement computes the sqrt.
The variable binding scheme in let is exactly the same as the argument binding scheme for lambdas. This means, you can decompose lists using let expressions as follows -
(let (((x y . xs) things))
(print "x = " x ", y = " y ", and the rest are " xs))
things is the list (1 2 3 4), the above expression will print x = 1, y = 2, and the rest are (3 4)
If any of the binding operations failed, the let block is not evaluated and the result is ().
(case object <match-cases>).
Syntax -
(case object (match-expr1 result1) (match-expr2 result2) ... (T else-result))
switch in C, but way more expressive. The result of a case expression is the result corresponding to the match-expr that succeeded in a match-bind operation against the given object.
In the simplest case, when object is a symbol, you can use case like switch as follows -
(case object ('one "One a penny") ('two "Two a penny") ('what "Hot cross buns"))
The match expressions are not limited to constants and are basically the same as the binding expressions for let or lambda. For example, here is a case expression to print the head of a list -
(case list-object ((x . xs) (print "Head of " list-object " is " x ".")) (() (print "Cannot take head of the empty list!")))
(x . xs) is bound against the given list-object. If list-object is a non-empty list, this bind operation will succeed and x will be bound to the head of the list and xs will be bound to the tail of the list. The second match expression is the empty list which will succeed in matching against another empty list only. So if list-object is the empty list, then the second print statement will be evaluated.
(apply fn arglist).
Equivalent to
(eval (cons fn arglist))
apply function lets you apply the given function to the given argument list, both of which may be values of other expressions.For example -
(define one-to-ten '(1 2 3 4 5 6 7 8 9 10)) (print (apply + one-to-ten))
The above code will print the sum of numbers from 1 to 10, and is equivalent to
(print (+ 1 2 3 4 5 6 7 8 9 10))
(eval s-expr).
Evaluates the given single s-expression and returns the result. For example,
(eval '(+ 2 3))
5. In this sense, eval is the counter part of quote.
(call/cc (fn (k) --- (k result) ---)).
Abbreviation for "call with current continuation", call/cc is an implementation of the scheme recommendation for continuation support. The first and only argument to call/cc is expected to be a function that takes a single argument called the continuation. call/cc will then call this function, supplying the current continuation as the argument.
A brief intro to continuations follows. When evaluating a sub-expression of any expression, the remainder of the computation may be thought of as a function that expects the result of the sub-expression evaluation. This "remainder of the computation" function w.r.t. the specific sub-expression is called the "continuation" at that point in the evaluation process.
The whole expression may be rewritten as a call to this continuation function with the argument as the result of the sub-expression under consideration. Note that the continuation function does not return a value to the context in which it is called. Instead, it "breaks out" of the context and pretends as though the result of the sub-expression is the argument supplied to the continuation function at invocation time.
Its time for an example - what'll the following code print? .. and then, what'll it print when bomb is defined to T instead?
(define bomb ())
(print (+ 1 2 (call/cc (fn (k)
(print "before\n")
(if bomb (k 0))
(print "after\n")
3))
4 5))
When bomb is (), the
(k 0)
before after 15
When bomb is changed to T, the if block will kick in and
(k 3)
call/cc which captured them, the (print "after\n")
call/cc block is not 3 as one would expect, but 0, because that's the argument given to the continuation function when it is invoked! So you'll get before 12
(print "after\n")
Continuations are rather powerful. The can be used to implement language constructs such as -
In general, it should be possible to store away the continuation function for future invocation. Early on in muSE's development, only a limited implementation of call/cc was put in in order to support breaking out of loops. Later on a full implementation of continuations was added. Now, you can store away the continuation function in a variable and invoke it as many times as you need to, because the continuation captures a complete snapshot of the execution environment at the time it is created.
1.4.7