8.12

4.5 Constructors🔗ℹ

A field in a class can have a keyword, default-value expression, or both. In that case, the class constructor accepts the argument in keyword form, makes the argument optional, or both. Keyword fields are printed with their keywords, too.

class Posn(~x: x, ~y: y = x)

> Posn(~y: 2, ~x: 1)

Posn(~x: 1, ~y: 2)

> Posn(~x: 1)

Posn(~x: 1, ~y: 1)

> def Posn(~y: y1, ~x: x1) = Posn(~x: 1, ~y: 2)

> y1

2

The keyword for a field does not have to match the name of the field as it is referenced by the . operator. Typically, the names are the same, and keyword fields support the same shothand as in function definitions where a keyword by itself implicitly supplies the corresponding identifier.

class Posn(~x, ~y)

> def p = Posn(~x: 1, ~y: 2)

> p.x

1

> p.y

2

class Cell(~row: i, ~column: j)

> def c = Cell(~row: 1, ~column: 2)

> c.i

1

> c.j

2

> c

Cell(~row: 1, ~column: 2)

Keyword and optional arguments tweak the default constructor that is implemented for a class, but a constructor clauses replaces the constructor completely. The syntax of constructor is like a fun expression form, but with constructor in place of fun. In the body of a constructor, super refers to a function that is like the default constructor, at least in the case of a class without a superclass.

class Posn(~x, ~y):

  nonfinal

  constructor

  | ():

      super(~x: 0, ~y: 0)

  | (~x: x, ~y: y):

      super(~x: x, ~y: x)

  | (~r: r, : θ):

      super(~x: r*math.cos(θ), ~y: r*math.sin(θ))

> Posn()

Posn(~x: 0, ~y: 0)

> Posn(~x: 1, ~y: 2)

Posn(~x: 1, ~y: 1)

> Posn(~r: 1, : 0.79)

Posn(~x: 0.7038453156522361, ~y: 0.7103532724176078)

Using the name super to access an underlying constructor makes a kind of sense, but that name can also be misleading: calling super does not create an instance of a superclass. (In this case, there is no superclass!) In fact, calling super may create an instance of a subclass. That’s because calling super in general continues the construction of an object that might have been started for instantiating this class or a subclass.

When a class has a superclass, super in a constructor calls the superclass constructor, and the arguments should match whatever is expected by the superclass’s constructor. The result, however, is not an instance of the class or superclass, but a function that expects arguments for the fields that are added to the new class. That second round of arguments should match the ones the that the default constructor would accept if it were for a class with no superclass. The super function needs a programmer to explicitly separate the two sets of arguments, because it might be ambiguous which arguments are for the superclass and which are for the new class, depending on the arguments allowed by the superclass constructor; currying is a simple way to enable that distinction.

In the example below, Posn3D adds a z field that is tagged with a ~z keyword, so the second set of arguments to super should have just one argument and use the ~z keyword. Meanwhile, the first set of arguments can take any of the forms that the Posn constructor supports.

class Posn3D(~z):

  extends Posn

  constructor

  | ():

      super()(~z: 0)

  | (~x: x, ~y: y, ~z: z):

      super(~x: x, ~y: x)(~z: z)

  | (~r: r, : θ, : φ):

      super(~r: r*math.cos(φ), : θ)(~z: r*math.sin(φ))

> Posn3D()

Posn3D(~x: 0, ~y: 0, ~z: 0)

> Posn3D(~x: 1, ~y: 2, ~z: 3)

Posn3D(~x: 1, ~y: 1, ~z: 3)

> Posn3D(~r: 1, : 0.79, : 0.314)

Posn3D(~x: 0.6694313056666457, ~y: 0.6756210605712837, ~z: 0.30886552009893214)

When Posn3D instances are created like this, the super calls in the Posn3D constructor first gather the two sets of arguments, and the the first set is passed on to the constructor of Posn. Within that invocation of the Posn constructor, calling super produces an the instance of Posn3D, not merely a Posn instance.