jackson@esosun.UUCP (Jerry Jackson) (07/26/88)
It occurred to me that there is an inconsistency in the scheme/lisp treatment of assignment when it comes to symbols -- in all other cases assignment means changing the contents of a cell of some kind, while assignment to a symbol is treated as a renaming operation. I figured this had resulted from the functional programming perspective -- a binding is simply the name for a value in a particular context... It seems non-intuitive that assigning to "a" is actually removing the name "a" from some value and naming a different value "a". I thought that a slightly different model might be useful -- Let binding be an irrevocable operation. Create a new data type called a "variable" that has the following property -- When a variable is returned from eval, it is dereferenced so that its contents are returned, except when it is the first argument to a set! special form or the result of a 'var' special form. My intent is that 'variable' is the only mutable data type -- I'll give a few examples before explaining the benefits of this model: (define fact (lambda (n) (if (< n 2) 1 (* n (fact (1- n)))))) defines fact as you would imagine and works the same way you would think -- however, following this with: (set! fact 5) would return an error -- fact is not bound to a variable After evaluating: (define fact2 (var (lambda (n) (if (< n 2) 1 (* n (fact2 (1- n))))))) (fact2 x) would give the same result as (fact x), but (set! fact2 5) would succeed. Note: typing "fact2" at this point would return 5, but the expression: (var? fact2) -> t while (var? fact) -> nil This produces several benefits: 1) the 'setf' form of CL is obtained for free in a much less kludgey way -- e.g. after: (define w (list (var 3) (var 4))) => w = (3 4) evaluating: (set! (cadr w) 5) would cause w to appear as: (3 5) 2) built-in functions can be defined at the top level without using the 'var' form so that they cannot be redefined... 3) the need for things like "named-lambda" is lessened -- If a recursive function is defined without using 'var', the value will never change and you get the effect of a named-lambda for free 4) assignment becomes completely consistent and only one assignment special form is needed instead of one for each type of mutable data -- It is no longer necessary to have vector-set!, set-car!,... 5) quoted constants are now actually guaranteed to be constant -- no more accidental program modification 6) inlining (or direct linking -- not through a symbol) of built-ins is now allowable without constraints Any comments? +-----------------------------------------------------------------------------+ | Jerry Jackson UUCP: seismo!esosun!jackson | | Geophysics Division, MS/22 ARPA: esosun!jackson@seismo.css.gov | | SAIC SOUND: (619)458-4924 | | 10210 Campus Point Drive | | San Diego, CA 92121 | +-----------------------------------------------------------------------------+
alti@tub-tfs.UUCP (Thorsten Altenkirch) (07/27/88)
() It occurred to me that there is an inconsistency in the scheme/lisp () treatment of assignment when it comes to symbols -- in all other () cases assignment means changing the contents of a cell of some kind, () while assignment to a symbol is treated as a renaming operation. I () figured this had resulted from the functional programming perspective () -- a binding is simply the name for a value in a particular context... () () It seems non-intuitive that assigning to "a" is actually removing () the name "a" from some value and naming a different value "a". I don't understand what you mean. You assign values to symbols and this is realized by modifying the innermost environment, where the symbol is bound. You can use this scheme to define any mutable data structure. Consider following implementation of cons (see also Abelson/Sussman,"The structure and the interpretation of Computer Programs", pp 207) : (define (my-cons x y) (lambda (m) (cond ((eq? m 'car) x) ((eq? m 'cdr) y) ((eq? m 'set-car!) (lambda (v) (set! x v))) ((eq? m 'set-cdr!) (lambda (v) (set! y v)))))) (define my-car (x) (x 'my-car)) (define my-cdr (x) (x 'my-cdr)) (define my-set-car! (x v) ((x 'set-car!) v)) (define my-set-cdr! (x v) ((x 'set-cdr!) v)) These implementation of cons is heavily influenced by the representation of data structures in the lambda calculus. But you have no assignment in the lambda calculus. I found it quite interesting how well a very general assignment and lambda calculus can live together (if you have call-by-name semantics). () I thought that a slightly different model might be useful. However your idea with the "var"- form is not so bad. (I only can't understand the reasons you gave). A thing like this is realized in ML (which is also a functional, but typed language, developed at Edinburgh, from Robin Milner et al.) They have a type 'a ref ('a is a type-variable) with the following operations : ref : 'a -> 'a ref ! : 'a ref -> 'a (op :=) : 'a ref * 'a -> unit (* op means infix & unit is novalue). A simple example (see R.Harpers "Introduction to Standard ML", p. 36): (- is what the user types in, > are the systems responses). - val x = ref 0; > val x = ref(0) : int ref - !x; > 0 : int - x := 3; > () : unit - !x; > 3 : int As might be clear you can use ref as a part of every data structure. () This produces several benefits: () () 1) the 'setf' form of CL is obtained for free in a much less () kludgey way -- e.g. after: () ... () 4) assignment becomes completely consistent and only one assignment () special form is needed instead of one for each type of mutable data -- () It is no longer necessary to have vector-set!, set-car!,... OK. But you export more about the implementation of a data type. No problems with vectors and things like this, but if you implement a more complex data type, you may decide to change the implementation w.o. changing the interface. That becomes impossible, if your export includes referential types. () 2) built-in functions can be defined at the top level without using the () 'var' form so that they cannot be redefined... () ... () 6) inlining (or direct linking -- not through a symbol) of built-ins () is now allowable without constraints I think that's more a point of the module structure of your program. You often use modules and you don't want to change them, not only the primitive functions. It should be possible to declare these different usages of modules. (CommonLisp has package to realize modules, but it lacks the possibility to declare a package as read-only). () 3) the need for things like "named-lambda" is lessened -- If a recursive () function is defined without using 'var', the value will never change () and you get the effect of a named-lambda for free named-lambda is a derivative from rec, and rec is the fixed-point combinator. I think this is the onliest semantical clean way tp define recursion. You should get an error if you use a variable on a point where it is not defined. (That's the way ML works). () 5) quoted constants are now actually guaranteed to be constant -- () no more accidental program modification This should be garanteed by the production system, independenly of the philosophy of assignments. Thorsten