SE-0310: Effectful Read-only Properties

I'd like to shed some light on my reasoning for the syntax chosen in this proposal.

First, let's consider some of places for effects specifiers that have been discussed or considered for a computed property:

<A> var prop: Type <B> {
  <C> get <D> { }
}
  • Position A is primarily used by access modifiers like private(set) or declaration modifiers like override. The more "effecty" mutating/nonmutating is only allowed in Position C, which precedes the accessor declaration, just like a method within a struct. Also, I don't think there's a happy ordering of effects specifiers and the other modifiers in Position A: neither override async throws var prop nor async throws override var prop reads well to me. For functions, the effects specifiers appear after the subject, not before it.

  • Position B doesn't make any sense to me, because effects are only carried as part of a function type, not other types. I think it would be very confusing, leading people to think Int async throws is a type, when that is not. Strega had suggested we use an arrow syntax, such as var prop async throws -> Bool { }, but the arrow would suggest that accessing the property yields a function, when it will be a boolean. This gets even more confusing for properties that are functions. At first glance, var predicate async throws -> ((Int) async throws -> Bool) { } looks like it returns a curried function.

  • Position C is not bad; it's only occupied by mutating/nonmutating, but placing effects specifiers here is not consistent with the positioning for functions, which is after the subject. Since Position D is available, it makes more sense to use that instead of Position C.

  • Position D is an unused place in the grammar, places the effects on the accessor and not the variable or its type, and is consistent with where effects go on a function declaration, after the subject: get throws and get async throws, where get is the subject. In addition, this position is away from the variable so it prevents confusion between the accessor's effects and the effects of a function being returned:

  var predicate: (Int) async throws -> Bool {
    get throws { /* ... */ }
}

The access of predicate may throw, but if it doesn't, it results in a function that is async throws. I can understand the desire to take advantage of the implicit-getter shorthand:

  var predicate: (Int) async throws -> Bool { /* ... */ }

but there's no good place for effects specifiers here, and I think that's OK. It's a short-hand / syntactic sugar, which necessarily has to trade some of its flexibility for conciseness. The full syntax for computed properties explicitly defines its accessors.

Now, let's move onto subscripts. I'll skip right to the major difference for subscripts, which is that they have a method-like header and support the implicit-getter short-hand, so that it resembles a method too:

class C {
  subscript(_ : InType) <E> -> RetType { /* ... */ }
}

Position E is a tempting place for effects specifiers for a subscript, but subscripts are not methods. They cannot be accessed a first-class function value with c.subscript, nor called with c.subscript(0); they use an indexing syntax c[0]. Methods cannot be assigned to, but subscript index expressions can be. Thus, they are closer to properties that can accept an argument.

Much like the short-hand for get-only properties, I think trying to find a position for effects specifiers on the short-hand form of get-only subscripts (whether its Position E or otherwise) will trap us in a corner once writable subscripts can support effects. Why? Position E a logically valid spot in the full-syntax and the short-hand syntax, and creating an inconsistency between the two would be bad. Then, using Position E + the full syntax creates an opportunity for confusion in situations like this:

  subscript(_ i : Int) throws -> Bool {
    get async { }
    set { }
 }

Here, the only logical interpretation is that set is throws and get is async throws. The programmer needs to look in multiple places to add up the effects in their head when trying to determine what effects are allowed in an accessor. This may not seem so bad in this short example, but consider having to skip over a large get accessor definition to learn about all of the effects the set accessor is allowed to have for this subscript, when you do not need to do that for a computed property.

So, I think that Position D should be the one true place where you can look to see whether there are effects for that type of accessor, both for subscripts and computed properties.

15 Likes