8.12

4.4 Fields and Properties🔗ℹ

A mutable declaration before a field enables assignment to the field using :=. An assignment can appear within methods using the field name directly, or it can appear on a . expression that accesses a field in an object.

class Posn(mutable x, mutable y):

  method go_home():

    x := 0

    y := 0

> def p = Posn(3, 4)

> p.x := 9

> p

Posn(9, 4)

> p.go_home()

> p

Posn(0, 0)

Extra fields can be added to a class with field clauses. These fields are not represented the class’s constructor, and so a field has an expression to provide the field’s initial value. A field added with field is always mutatble, and := can be used to assign to the field.

class Posn(x, y):

  field name = "Jane Doe"

> def p = Posn(3, 4)

> p.name := "Dotty"

> p.name

"Dotty"

Sometimes, you want a value associated to an object that is not stored in a field, but is still accessed and assigned with field-like notation. A property is like a field in that it is accessed without method-call parentheses, and it can also support assignment via :=. A property is different from a field, however, in that access and assignment can trigger arbitrary method-like comptation, and a property implementation can be overridden in a subclass.

A read-only property is written similar to a method, possibly with a result annotation, but without parentheses for arguments.

class Posn(x, y):

  property angle :: Real:

    math.atan(y, x)

  property magnitude :: Real:

    math.sqrt(x*x + y*y)

> Posn(3, 4).magnitude

5

> Posn(4, 4).angle

0.7853981633974483

> Posn(4, 4).angle := 0.0

angle: property does not support assignment

To define a property that supports assignment, use | similar to defining a function with fun and multiple cases. The first case should look like a simple property clause to implement property access, while the second case should resemble an assignment form. In the assignment-like form to define a property, the part after := is a binding position, just like a function or method argument.

class Posn(mutable x, mutable y):

  property

  | angle :: Real:

      math.atan(y, x)

  | angle := new_angle :: Real:

      let m = magnitude

      x := m * math.cos(new_angle)

      y := m * math.sin(new_angle)

  property magnitude :: Real:

    math.sqrt(x*x + y*y)

> def p = Posn(4, 4)

> p.magnitude

5.656854249492381

> p.angle

0.7853981633974483

> p.angle := 0.0

> p

Posn(5.656854249492381, 0.0)

A property can be a good choice for derived values like magnitude and angle in a Posn, because they require relatively little computation and are deterministically derived from the object’s fields. A property is probably not a good choice for a lookup action that involves visible side effects or that involves a lot of computation, because those features defeat the expectation of someone reading the code, because they see a field-like access.