[4/21/99] =============================================================================== * Type conversion/coercion: Another useful generic that you might want to define for new classes is the "as" generic. It is used to convert an object to a class. The unconventional thing about these methods is that they specialize on class objects rather than on instances. For example: -------------------- (defclass () (x :initarg :x :accessor getx) (y :initarg :y :accessor gety)) (defmethod (as (c = ) (p )) (echos-ns "<" (getx p) "," (gety p) ">")) (as (make :x 1 :y 2)) --> "<1,2>" -------------------- Note that it is _your_ responsibility to return the correct type, you could write nonsense definitions such as: -------------------- (defmethod (as (c = ) (p )) 42) -------------------- =============================================================================== * Something on print-object - just mention that it can be redefined but it needs a variable amount of variables so we won't learn about it officially. =============================================================================== * Reminder about environments: * Environment: An environment is a linked list of frames. * Frame: A frame is a list of bindings. * Binding: A binding is a variable symbol and its bound value. * Symbol Evaluation: To get the value of a symbol you look for the first frame that contain a binding for it in the current environment. * The Global Environment: There is a "global" environment that contains the bindings for all global symbols (all functions). This is where the REPL loop work. * "define": Global "define" forms bind new global values. You can think about internal forms as defining things in the "current" environment. * Closure: A function is called a "closure" - it is a pair of an environment pointer and a body (actually an argument list and a body). When a function is being constructed (using the "lambda" special form and its "method" relative) then this pair is being constructed. * Lexical Scoping: This is called "lexical scoping" - if you want to know what variable you're using - just look at the lexical structure of your program. Example: -------------------- (define x 3) (define get-x (lambda () x)) (define foo (lambda (x) (get-x))) (foo 4) --> 3 -------------------- This is in contrast to dynamic scoping for example, which uses a single environment that is extended with the call stack. Lexical scoping is enabled by using closures. * Evaluation: Evaluation is still done in the same way, except that we have a mechanism for getting variable values and modifying them. This is still applicative order - a function is applied only after evaluating its actual parameters. * Application: The application stage is modified - using environments instead of substitutions. When a function is being applied - you open a new frame (sometimes I call this "opening a new environment"), in this frame you bind the _formal_ arguments of the function to the values supplied - the _actual_ arguments. Then the body is evaluated in this new environment. See also the stack below. * "let": The explanation we had ages ago - translating "let" forms to applications of temporary anonymous functions can also explain what happens with let forms now - you just open a new environment with the values bound to the variables using an intermediate anonymous closure on the way (you can skip drawing it when you know how things work). * Stack: This is not all of the thing the computer needs for evaluation... We also need a stack for remembering the functions that we should "return" to. This stack is necessary and sometimes can be added to the environment diagrams (as dotted lines), but you should really know not only what fram to go back to but the point in the code that you were evaluating and continue from there. * REPL: Scheme uses a REPL - Read-Eval-Print-Loop. This reads an expression, evaluates it, prints the result and loops back. The internal REPL uses the global environment. * Eyes: I usually draw a closure with "eyes" - two pointers - one for the body (containing the arguments and code) and one for the environment. * Frame Graph: During computations we might have a general graph of frames - not a single thread of them - this is different from tranditional languages such as Pascal etc where the environments always correspond to the runtime stack. The reason for this is the fact that Scheme handles functions (closures) as first-class objects. * "set!": "set!" is a special form that you can use to modify the value of a variable binding - the binding that is modified is the nearest in the current environment - the one that will be used to get the value of this variable. This is for forms that set! a variable - forms that set! some subvalue of a variable (such as (set! (head ...) ...)) are translated to function calls (such as set-car! etc). =============================================================================== * Examples for evaluations using environments: -------------------- (define (mult x y) (* x y)) (define (double x) (mult 2 x)) (double 7) -------------------- =============================================================================== * There are two facts that enables the usage of closures as data constructors: - The evaluation of a function is always in a newly created frame with the local variables. - If we create a function, it will have a pointer to the environment where it was defined. So a function that returns a function will do the following on _each_ call: - Create an environment where you can bind variables. - Create a closure that points to this environment. This means that each application creates a closure with a different environment with different variable values. Here is an example: -------------------- (define make-mult (lambda (x) (lambda (y) (* x y)))) (define double (make-mult 2)) (double 7) (define triple (make-mult 3)) (triple 7) -------------------- =============================================================================== * A more complicated example: -------------------- (define kons (lambda (x y) (lambda (s) (s x y)))) (define kar (lambda (x) (x (lambda (x y) x)))) (define kdr (lambda (x) (x (lambda (x y) y)))) (define x (kons 1 2)) (kar a) -------------------- ===============================================================================