=

Assignment works with variables, tables, and strings:

arc> (= x 1 y (table) z "string") arc> (= (y "key") "value") "value" arc> (= (z 1) #\p) arc> x 1 arc> y #hash(("key" . "value")) arc> z "spring"

arc> (= l '(a (b c) d)) arc> (= (car (cadr l)) 'z) arc> l (a (z c) d)

=

At this point, you may be wondering how assignment actually works. It looks like it's looking up the value in the table, string, or list, and then changing that value. You might wonder why (= (z 1) #\p) modifies a character inside z , instead of modifying the character returned by (z 1) Perhaps something like a C++ pointer or reference is being used?

The actual implementation is rather surprising. Assignment uses the setform macro, which takes apart the target expression, "reverse engineers" how to set a value at that place, and then builds code to perform the desired set operation. Macro expansion illustrates what is actually happening:

arc> (macex '(= (car (cadr l)) 'z)) ((fn () (atwith (gs67 (cadr l) gs68 (quote z)) ((fn (val) (scar gs67 val)) gs68))))

(scar (cadr l) val)

val

arc> (macex '(= (z 1) #\b)) ((fn () (atwith (gs69 z gs71 1 gs72 #\b) ((fn (gs70) (sref gs69 gs70 gs71)) gs72))))

(sref z #\b 1)

Note that the generated code has little resemblance to the original assignment, and the code for a list assignment is very different from the code for a string assignment.

The details

=

expand=list

expand=

expand=

set

expand=

setforms

The core of assignment is setforms , which performs the analysis on a place. It returns a list of length three: (binds val setter) , where val is a "getter" to return the current value at the place, setter is a function that takes the new value and assigns the new value to the place, and binds are variable bindings used by val and setter .

For example:

arc> (setforms 'x) ((gs75 x) gs75 (fn (gs76) (set x gs76)))

x

x

A more complex example also illustrates the variable bindings, the getter form, and the setter function:

arc> (setforms '((car (x 3)) 2)) ((gs2484 (car (x 3)) gs2486 2) (gs2484 gs2486) (fn (gs2485) (sref gs2484 gs2485 gs2486))) arc> (setforms '((car (x 3)) 2)) ((gs84 (car (x 3)) gs86 2) (gs84 gs86) (fn (gs85) (sref gs84 gs85 gs86)))

The table of setters

setforms

setter

arc> setter #hash((cddr . # ) (caar . # ) (cdr . # ) (cadr . # ) (car . # ))

arc.arc

arc> (= x '(a b c)) (a b c) arc> (= (nthcdr 1 x) '(d)) Error: "procedure /c/mzscheme/ac.scm:1062:12: expects 3 arguments, given 4: # (d . nil) 1 (a b c . nil)"

setters

The contents of the table are somewhat complex. This table uses the outermost form of the place as key, and returns a procedure that, given an expression, will generate the setforms result for that expression. For example, to set the place (cdr (car x)) :

arc> ((setter 'cdr) '(cdr (car x))) ((gs2593 (car x)) (cdr gs2593) (fn (val) (scdr gs2593 val)))

setter

defset

arc.arc

car

(defset car (x) (w/uniq g (list (list g x) `(car ,g) `(fn (val) (scar ,g val)))))

defset

setforms

setforms

atwith

fn

arc> (setforms '(car y)) ((gs2413 y) (car gs2413) (fn (val) (scar gs2413 val))) arc> (macex '(= (car y) 42)) ((fn () (atwith (gs2416 y gs2417 42) ((fn (val) (scar gs2416 val)) gs2417))))

Confusingly, the arc.arc code uses "setter" both as the name of the global table, and as the name of the setter function returned by setforms .

The implementation of setforms

setforms

setter

Handling an expression that is a symbol is relatively straightforward. For a symbol with "special syntax", ssexpand is called to expand the special syntax, and setforms is tried again. For a regular symbol, setforms returns a straightforward set expression.

For a "metafn" expression ( compose or complement ), expand-metafn-call and ssexpand are applied and setforms is called again.

The third path is where setforms does its work. If the setter table has an entry for the outermost form, then setforms is done. Otherwise, setforms treats the expression as a data structure being indexed. (For example, (tbl 'key) to index into a table.) It generates the list of "binds", "getter", and "setter", where the getter is a simple invocation of the data structure and the setter uses sref . (Strangely, setforms supports multiple arguments to the data structure, even though sref does not. setforms also prints a warning if the expression is a function call.

Adding a new place type

defset

setforms

For example, suppose it is desired to make the last element of a list, (last g) , a place that Arc can handle. The getter form is straightforward, and the last element could be set with (sref g val (- (len g) 1)) . This is expressed to defset as:

(defset last (x) (w/uniq (g) (list (list g x) `(last ,g) `(fn (val) (sref ,g val (- (len ,g) 1))))))

arc> (= x '(a b c d e)) arc> (= (last x) 'z) arc> x (a b c d z)

arc> (= y '((a b c) d e (f g h))) ((a b c) d e (f g h)) arc> (= (last (car y)) 'lastcar) arc> (= (car (last y)) 'carlast) arc> y ((a b lastcar) d e (carlast g h))

Another example shows how files can be made into places. The following code implements support for files as places; the expression (file "foo") will let the file "foo" be used as a place.

(def file (x) (readfile1 x)) (defset file (x) (w/uniq (g) (list (list g x) `(readfile1 ,g) `(fn (val) (writefile1 val ,g)))))

=

arc> (= (file "foo") 42) 42 arc> (++ (file "foo")) 43 arc> (= (file "bar") 100) 100 arc> (swap (file "foo") (file "bar")) 43 arc> (file "foo") 100 arc> (file "bar") 43

Generalized variables in Lisp

Many of the operations in Arc have analogous operations in Common Lisp. Arc uses = to set a place to a value, while Common Lisp uses setf. Arc's setforms is similar to Common Lisp's get-setf-expansion. The list of binds, val, and setter returned by setforms is similar to the 5 values making up a "setf expansion" in Common Lisp, where binds combines the list of temporary variables and the list of value forms, val is the accessing form, and setter takes the role of the storing form and list of store variables. Arc's defset is similar to Common Lisp's define-setf-expander.

Operations to support places

setforms place Generates the getter and setter code to access a place. >(setforms '(car x)) ((gs2459 x) (car gs2459) (fn (val) (scar gs2459 val))) setter (tests) defset name parms [body ...] Defines a new type of place. Creates a new setforms handler for the function of the specified name. >(defset foo (x) nil) #<procedure:...t/private/kw.rkt:592:14> metafn x Helper predicate for setforms. Tests if x is a meta function: that is, it has special syntax, or uses compose or complement. >'(metafn '(a:b)) (metafn (quote (a:b))) expand-metafn-call f args Helper for setforms. Take a function of the form (compose ...) and expands it into the approprate composed function calls.. >(expand-metafn-call '(compose g h) '(a b)) (g (h a b)) expand= place val Helper for =. Generates code to assign val to place. >(expand= 'a 42) (assign a 42) expand=list terms Helper for =. Pairs up the arguments to =, and calls expand= on each pair. >(expand=list '(a 42 b 43)) (do (assign a 42) (assign b 43))

or= place val Similar to the or macro, except assigns val to place if place is nil. New in arc3. >(let x nil (or= x 4) (or= x 5) x) 4

Legend

Copyright 2008 Ken Shirriff.