-
Notifications
You must be signed in to change notification settings - Fork 112
TM001 Syntax and semantics for structural mutation of objects
Currently, the only way to emulate structure mutation in Pyret is to close over variable mutation. This is insufficient for representing a number of patterns we'd like to support, and doesn't generalize to things like `shared' in the future.
data Node:
| node(data :: Any, mutable edges :: List<Node>)
end
n1 = node("n1", [])
n2 = node("n2", [n1])
n1 <- { edges : [n2] }
data Student:
| full-time-student(name :: String, mutable classes :: List<Class>)
end
data Class:
| lecture(name :: String, mutable students :: List<Student>)
end
s = full-time-student("Joe", [])
c1 = lecture("Underwater Basket-Weaving", [])
c2 = lecture("Wicker-Drying", [])
fun enroll(student, course):
when (course.member(student).not()):
student <- { classes : student.classes.append([course]) }
course <- { students : course.students.append([student]) }
end
end
enroll(s, c1)
enroll(s, c2)
This TM proposes adding
- A new type of runtime value, with several related functions
- A new modifier on object and variant fields, implemented via desugaring to constructs from 1
- A new expression form for structure mutation, implemented via desugaring to constructs from 1, and a change to the `.' expression
Add boxes as new runtime values, via the following interface:
box<a>(v :: a,
read-pred :: (Any -> Bool),
write-pred :: (Any -> Bool))
-> Box<a>
Creates a new value of a new runtime type, the box type, which holds a mutable reference cell, and two lists of functions, one containing only read-pred and the other containing only write-pred. The box object has no fields visible to the Pyret programmer.
set-box!<a>(b :: Box<a>, v :: a) -> nothing
Update the value in box b to v if all the write-preds of b return true when applied to v. Implementations are encouraged to skip the calls to the write-preds if they are functional and would provably return true when applied to v.
unbox<a>(b :: Box<a>) -> a
Return the value v in box b if all the read-preds of b return true when applied to v. Implementations are encouraged to skip the calls to the read-preds if they are functional and would provably return true when applied to v.
copy-box<a>(b :: Box<a>,
read-pred :: (Any -> Bool),
write-pred :: (Any -> Bool))
-> Box<a>
Create a new box holding the same mutable reference as b, and with the provided read- and write-pred appended to the respective lists of predicates in b.
Extend the syntax of fields in variants with the `mutable' keyword:
variant-binding := (mutable) name (:: annotation)
In variants, the body of the constructor is desugared to call box() on the initial value provided, and with read- and write- predicates that are compiled from the annotation.
Add this new expression:
e <- { f1 : e1 [f2 : e2, ] ... }
==>
%obj = e
%f1-computed = f1 # (needed if f1 is a computed field name)
%e1-computed = e1
...
set-box!(%obj:[%f1-computed], %e1-computed)
...
This has a few nice properties:
-
If there are any runtime errors in any of the field names, field values, or the object expression, no side effects from the set-box! occur (side effects from evaluating expressions before the exceptions still happen).
-
Later expressions cannot observe side effects on the object from earlier expressions. E.g. in
obj <- { f: 5, g: obj.f }
The value for g will not see the update to f.
Second, the dot expression is updated to auto-unbox references that appear as fields:
e_obj.x
== compiles to ==>
(let ((%obj e_obj)
(%fld (get-field %obj "x"))
(cond
[(method? %fld) (%fld obj)]
[(box? %fld) (unbox %fld)]
[else %fld])))