Skip to content

TM001 Syntax and semantics for structural mutation of objects

Joe Politz edited this page May 15, 2013 · 1 revision

TM001 - Syntax and semantics for structural mutation of objects

Motivation

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.

Examples:

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)

Specification:

This TM proposes adding

  1. A new type of runtime value, with several related functions
  2. A new modifier on object and variant fields, implemented via desugaring to constructs from 1
  3. A new expression form for structure mutation, implemented via desugaring to constructs from 1, and a change to the `.' expression

Boxes

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.

Mutable keyword

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.

Object update expression and dot expression

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])))