Structure mutators and set!-expressions are related. Indeed, in
section
we explained the effects of the first
with the second. Still, there are also important differences that a
programmer must understand. Let's start with the syntax:
(set!![]()
)
-
-
![]()
![]()
A set!-expression is an expression that consists of two pieces: a variable and an expression. The variable is fixed; it is never evaluated. The expression is evaluated. In contrast, a structure mutator is a function. As such, it is a value that the program can apply (to two arguments), pass to other functions, store inside of structures, and so on. Structure mutators are created in response to structure definitions, just as structure constructors and selectors.
Next we must consider lexical scope issues (see section
).
A set!-expression contains a variable. For the set!-expression to be valid, this
variable must be bound. The connection between a set!-expression's variable and
its binding occurrence is static and can never be changed.
The scope of a mutator is that of its corresponding define-struct. Thus, in the following program
(define-struct aaa (xx yy))the underlined occurrence of define-struct has a limited lexical scope, and its scope is a hole in the scope of the top-level define-struct. A result of this scoping is that the mutator for the top-level define-struct cannot mutate the structure called UNIQUE. The two mutators are unrelated functions that coincidentally have the same name; the rules for the evaluation of local-expression dictate that we rename one consistently.(define UNIQUE (local (
) (make-aaa 'my 'world)))
...
To highlight the differences in syntax and lexical scope, take a look at the following two, apparently similar programs:
(define the-point (make-posn 3 4))(set! x 17)
(define the-point (make-posn 3 4))(set-posn-x! the-point 17)
The one on the left is illegal, because the x in the set!-expression is an unbound variable. The program on the right is perfectly legal; it refers to the field x of a posn structure.
The largest difference between set!-expressions and mutators concerns their semantics. Let's study two examples to understand the differences once and for all. The first illustrates how similar looking expressions evaluate in a radically different manner:
(define the-point (make-posn 3 4))(set! the-point 17)
(define the-point (make-posn 3 4))(set-posn-x! the-point 17)
The program on the left consists of a definition for the-point and an assignment to the-point; the one on the right starts with the same definition for the-point followed by an application of the mutator. The evaluation of both affects the variable definition but in different ways:
(define the-point 17)(void)
(define the-point (make-posn 17 4))(void)
On the left, the-point now stands for a number; on the right, it is still a posn structure but with a new value in the x-field. More generally, a set!-expression changes the value on the right-hand side of a definition, and the application of a mutator changes the value of just one field in a structure that occurs on the right-hand side of a definition.
The second example shows how an application of mutator evaluates the arguments, which is not the case for set!-expressions:
(define the-point (make-posn 3 4)) (define an-other (make-posn 12 5))(set! (cond [(zero? (point-x the-point)) the-point] [else an-other]) 1)
(define the-point (make-posn 3 4)) (define an-other (make-posn 12 5))(set-posn-x! (cond [(zero? (point-x the-point)) the-point] [else an-other]) 1)
Whereas the program on the left is illegal, because a set!-expression must contain a fixed variable in the second position, the one on the right is legitimate. The evaluation of the program on the right changes the x-field in an-other to 1.
Finally, mutators are values, which means a function can consume a mutator and apply it:
;; set-to-2 : S-mutator S-structure -> void ;; to change a field in s to 2 via mutator (define (set-to-2 mutator s) (mutator s 2))The function set-to-2 consumes a mutator and a structure that the mutator can modify. The program uses it to change the x-field in a posn structure and the ww-field in a bbb structure. In contrast, if we were to apply a function to a set!-expression, it would receive (void) and nothing else.(define-struct bbb (zz ww))
(local ((define s (make-posn 3 4)) (define t (make-bbb 'a 'b))) (begin (set-to-2 set-posn-x! s) (set-to-2 set-bbb-ww! t)))
Mixing set! and Structure Mutators: When a program uses both set!-expressions and structure mutators, our evaluation rules fail for some cases. Specifically, they don't explain sharing properly. Consider this program fragment:
(define the-point (make-posn 3 4))According to our rules, the two definitions refer to the same structure. The second one does so by indirection. The set!-expression changes what the-point stands for, but it shouldn't affect the second definition. In particular, the program should produce true. If we were to use our rules in a naive manner, we would not be able to validate this point.(define another-point the-point)
(begin (set! the-point 17) (= (posn-x another-point) 3))
A proper explanation of structures must introduce a new definition for every application of a structure constructor, including those on the right-hand side of definitions in the original program. We will place the new definitions at the beginning of the sequence of definitions. Furthermore, the variable in the new definition must be unique so that it cannot occur in a set!-expression. We will use variables such as struct-1, struct-2, and so on, and agree to use them for this purpose only. These names, and only these names, are values.
Using the minor changes to our rules, we can evaluate the program fragment properly:
(define the-point (make-posn 3 4))(define another-point the-point)
(begin (set! the-point 17) (= (posn-x another-point) 3))
= (define struct-1 (make-posn 3 4))
(define the-point struct-1)
;; evaluate from here:
(define another-point the-point)
(begin
(set! the-point 17)
(= (posn-x another-point) 3))
= (define struct-1 (make-posn 3 4))
(define the-point struct-1)
(define another-point struct-1)
;; evaluate from here:
(begin
(set! the-point 17)
(= (posn-x another-point) 3))
At this point, the structure is created, and both of the original variables
refer to the new structure. The rest of the evaluation changes the
definition of the-point but not another-point:
...
= (define struct-1 (make-posn 3 4))
(define the-point 17)
(define another-point struct-1)
;; evaluate from here:
(begin
(void)
(= (posn-x another-point) 3))
= (define struct-1 (make-posn 3 4))
(define the-point 17)
(define another-point struct-1)
;; evaluate from here:
(= (posn-x another-point) 3)
= (define struct-1 (make-posn 3 4))
(define the-point 17)
(define another-point struct-1)
;; evaluate from here:
(= 3 3)
The final result is true, as expected.
The modified evaluation rules are a bit more cumbersome than the old ones. But they fully explain the difference between the effects of set!-expressions and those of structure mutation, which, for programming in modern languages, is an essential concept.