[4/28/99] =============================================================================== * Here is an interesting example: A simple definition of add1 (a function that returns a number + 1) can be the following: -------------------- (define add1 (lambda (x) (+ x 1))) -------------------- This is very simple, but here is another variation that would be more robust -- why? -------------------- (define add1 (let ((+ +)) (lambda (x) (+ x 1)))) -------------------- =============================================================================== * A last example for environments: when doing recusrion - we get a list of frames, that are linked by the "stack" pointer, but the _frames_ all point to a single frame. A simple example: -------------------- (define (fact n) (if (<= n 1) n (* n (fact (1- n))))) (fact 3) -------------------- When tail recursion is used, the thing to note is that every tail-recursive call can be replaced by code that only modifies the variables and loops back, eliminating the need for playing with environments. =============================================================================== * We've seen the usage of quote as a tool to get symbols as in 'x, and lists as in '(a b c). When we talk about this, there are two elements of the Scheme interpreter that should be discussed - the reader and the writer: - The Scheme reader, represented by the read function is responsible for reading and processing input, for example, when it reads the string "x" it returns the symbol x, and "(+ 1 2)" returns the list with the symbol +, and the two integers 1 and 2. - The Scheme writer, represented by the write function is translating some Scheme value to a displayable form that usually can be read back to get the same value (or at least an equivalent value). For example, (cons 1 2) is shown as "(1 . 2)" and if you'll evaluate (read) and enter the same string, you will get a similar value. Now continue with quotes: Whenever the Scheme reader sees a quote character, it will actually translate it to the quote special form, so these two things are actually being read as (quote x) and (quote (a b c)). The Scheme writer then manages these by showing you the quote character whenever it prints a list with the quote symbol, here is an example: -------------------- (quote (quote x)) --> 'x (list 'quote 'x) --> 'x (head (read)) Enter "'a" --> quote -------------------- =============================================================================== * The thing to be noted here is that with quote you simply write a piece of Scheme code with a quote prefix and what you get is a list - but Scheme programs uses lists, so actually, quote gives us a way to manage Scheme code. This is a fact that makes more games available in Scheme, for example, here is an expression that prints itself: -------------------- ((lambda (x) (list x (list 'quote x))) '(lambda (x) (list x (list 'quote x)))) -------------------- When we talk about a list that represent Scheme code, we call it an "S-expression", but this is only a simple list - the main point is that Scheme syntax is represented by lists. =============================================================================== * A fact that was discussed several times now, is that there is no way to have a function that will change the value of a variable. The following function: -------------------- (define (inc! x) (set! x (add1 x))) -------------------- will get an integer value, but what you increase it, only the internal variable is changed. We also saw that boxed values are actually pointers, and that we could make a function modify the internal components of such values, but _not_ the pointer itself since if it was changed, then again, only the local copy got modified. This is the result of the way that the Scheme interpreter works - every function call opens a new environment to evaluate things in, and all arguments are passed by value. =============================================================================== * So, is there any way to modify variables in such a way? A possible solution might come somehow from the fact that S-expressions are simply lists - we could construct a function that will get a symbol and return an expression that will increment this variable: -------------------- (define (make-inc!-form var) (list 'set! var (list '+ var 1))) -------------------- Note that this function gets a symbol as input, and returns the desired S-expression: -------------------- (make-inc!-form 'x) --> (set! x (+ x 1)) -------------------- However, this is not enough, we still need more things. =============================================================================== * What we need now is some way to evaluate this form - this is a major question: do we have some way to call a "low-level" Scheme function that will evaluate a form? Having something like this available means that we have considerable power - we can use Scheme to evaluate our own expressions, otherwise we would have to implement a whole interpreter (e.g., PS#6). So, we actually have such a Scheme function - eval. You apply it to an expression and it will evaluate it. Here is an example of using it: -------------------- (define x 3) (eval 'x) --> 3 ; note - here we move from the syntax x to its meaning (eval 3) --> 3 (eval '(set! x 8)) x --> 8 -------------------- These examples demonstrate that eval is actually taking in a piece of syntax, and get its _meaning_. The (eval 3) example demonstrate that the syntax for numbers are the numbers themselves. To demonstrate the fact that this actually allows us to use the language, there are commercial Lisp compilers that you can buy, and some provide a "runtime" engine that you can distribute to your clients to run your programs. But - having eval around means that you could give your client an application that will run eval, therefore giving them your Lisp compiler for free! This is the reason that on such commercial tools, the runtime engine have some limitations such as not having any eval function. =============================================================================== * Now we can combine eval with the above make-inc!-form function: -------------------- (define x 10) (make-inc!-form 'x) --> (set! x (+ x 1)) (eval (make-inc!-form 'x)) x --> 11 (define (inc!-sym var) (eval (make-inc!-form var))) (inc!-sym 'x) x --> 12 -------------------- However: -------------------- (let ((x 2)) (inc!-sym 'x) x) --> 2 x --> 13 -------------------- What happened? Whenever eval is doing something, it always starts from the global environment, so this solution does not help us too much. =============================================================================== * So, how will we accomplish this? What we need is a way to read an expression and translate that expression locally to the appropriate set! form, for example this: (let ((x 2)) (inc! x) x) gets rewritten as this: (let ((x 2)) (set! x (+ x 1)) x) There actually is a Scheme feature that allows doing this, the macro mechanism. Macros are functions that Scheme treats in a very special way: - When Scheme reads an expression, just before evaluating it, the expression is "compiled" - translated to some form that is stored in memory. - At this stage, any symbol that stands in a function position (first in a list) is checked to see if it is bound to a macro. - If it is bound to a macro, then the macro function is applied on the contents of this S-expression. - Note that the arguments that are supplied to the macro function are S-expressions - pieces of syntax. This is the only reasonable way, since at this point we don't evaluate any code yet (we do evaluate something - the macro code, not the code that is being compiled now). This sounds a little complicated, but an example will simplify things - here is the way that we define macros (using defmacro) and use them: -------------------- (defmacro (foo) (echo "Hey!") 1) (foo) --> print "Hey!" and return 1 (define (bar) (foo)) ; prints "Hey!" - the macro was applied now (bar) --> 1 (defmacro (foo exp) (echo "exp =" exp) (list 'list exp)) (define (bar) (foo z)) ; prints "exp = z": the input is the symbol (bar) ; error: z is undefined -------------------- Now it is finally possible to define our inc! as a macro: -------------------- (defmacro (inc! v) (list 'set! v (list '+ v 1))) (define x 1) (inc! x) x --> 2 (let ((x 0)) (inc! x) x) --> 1 x --> 2 -------------------- Note that in the macro definition, the 1 is used as a syntax, but there is no need to quote it since in Scheme the syntax for numbers are the numbers themselves, they appear literally in the function code. ===============================================================================