Skip to content

Arrow Types aka Viewpoints

When we talked about reference capability composition and viewpoint adaptation, we dealt with cases where we know the reference capability of the origin. However, sometimes we don’t know the precise reference capability of the origin.

When that happens, we can write a viewpoint adapted type, which we call an arrow type because we write it with an ->.

Using this-> as a viewpoint

A function with a box receiver can be called with a ref receiver or a val receiver as well since those are both subtypes of box. Sometimes, we want to be able to talk about a type to take this into account. For example:

class Wombat
  var _friend: Wombat

  fun friend(): this->Wombat => _friend

Here, we have a Wombat, and every Wombat has a friend that’s also a Wombat (lucky Wombat). In fact, it’s a Wombat ref, since ref is the default reference capability for a Wombat (since we didn’t specify one). We also have a function that returns that friend. It’s got a box receiver (because box is the default receiver reference capability for a function if we don’t specify it).

So the return type would normally be a Wombat box. Why’s that? Because, as we saw earlier, when we read a ref field from a box origin, we get a box. In this case, the origin is the receiver, which is a box.

But wait! What if we want a function that can return a Wombat ref when the receiver is a ref, a Wombat val when the receiver is a val, and a Wombat box when the receiver is a box? We don’t want to have to write the function three times.

We use this->! In this case, this->Wombat. It means “a Wombat ref as seen by the receiver”.

We know at the call site what the real reference capability of the receiver is. So when the function is called, the compiler knows everything it needs to know to get this right.

Using a type parameter as a viewpoint

We haven’t covered generics yet, so this may seem a little weird. We’ll cover this again when we talk about generics (i.e. parameterised types), but we’re mentioning it here for completeness.

Another time we don’t know the precise reference capability of something is if we are using a type parameter. Here’s an example from the standard library:

class ListValues[A, N: ListNode[A] box] is Iterator[N->A]

Here, we have a ListValues type that has two type parameters, A and N. In addition, N has a constraint: it has to be a subtype of ListNode[A] box. That’s all fine and well, but we also say the ListValues[A, N] provides Iterator[N->A]. That’s the interesting bit: we provide an interface that let’s us iterate over values of the type N->A.

That means we’ll be returning objects of the type A, but the reference capability will be the same as an object of type N would see an object of type A.

Using box-> as a viewpoint

There’s one more way we use arrow types, and it’s also related to generics. Sometimes we want to talk about a type parameter as it is seen by some unknown type, as long as that type can read the type parameter.

In other words, the unknown type will be a subtype of box, but that’s all we know. Here’s an example from the standard library:

interface Comparable[A: Comparable[A] box]
  fun eq(that: box->A): Bool => this is that
  fun ne(that: box->A): Bool => not eq(that)

Here, we say that something is Comparable[A] if and only if it has functions eq and ne and those functions have a single parameter of type box->A and return a Bool. In other words, whatever A is bound to, we only need to be able to read it.