8.12

4.10 Mutual Dependencies🔗ℹ

The class and interface forms define names that can be used as annotations, but the rules about defining annotations before using them can be subtle. For example, this works:

> block:

    def make:

      fun () :: Posn:

        Posn(1, 2)

    class Posn(x, y)

    make()

Posn(1, 2)

But this does not:

> block:

    fun make() :: Posn:

      Posn(1, 2)

    class Posn(x, y)

Posn: not bound as an annotation

The reason the first example above works, however, is the same as the reason this one does not:

> block:

    def make:

      fun () :: Posn:

        Posn(1, 2)

    class Posn(x, y)

    use_static  // causes the next line to fail

    make().x

x: no such field or method (based on static information)

When you use fun as a definition form to bind a function id_name with a result annotation, the static information associated with that result annotation needs to be determined to bind id_name. That’s why the middle example fails. Using a fun expression on the right-hand side of def to bind id_name doesn’t need to inspect the result annotation to create the binding, but it also does not propagate that static information.

For this small example, the solution is straightforward, and similar to the one in Class Namespaces: define Posn before trying to use it:

> block:

    class Posn(x, y)

    fun make() :: Posn:

      Posn(1, 2)

    use_static

    make().x

1

This “just define it before” strategy does not work, however, when you have two classes that need to refer to each other.

> block:

    class Posn2D(x, y):

      method inject() :: Posn3D:

        Posn3D(x, y, 0)

    class Posn3D(x, y, z):

      method project() :: Posn2D:

        Posn2D(x, y)

Posn3D: not bound as an annotation

The problem here is that the annotation facet of class is bundled together with the method-declaration facet of class, so they canot be ordered differently. To enable mutual references, use the class.together form to combine the definitions.

> block:

    class.together:

      class Posn2D(x, y):

        method inject() :: Posn3D:

          Posn3D(x, y, 0)

      class Posn3D(x, y, z):

        method project() :: Posn2D:

          Posn2D(x, y)

    use_static

    Posn2D(1, 2).inject().project()

Posn2D(1, 2)

The class.together form can also be used when references among classes only go in one way, but you’d prefer to define the referencing class before the referenced class, and it can resolve the problem of referencing a class from definitions within the class form as discussed in Class Namespaces.

The class.together form works by using annot.delayed_declare and annot.delayed_complete. You can use annot.delayed_declare and annot.delayed_complete directly instead of class.together, and that approach allows declarations to span different modules. Within a module, prefer class.together, because it avoids potential pitfalls of using annot.delayed_declare and annot.delayed_complete directly.