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 likeoverride
. 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: neitheroverride async throws var prop
norasync 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 asvar 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
andget async throws
, whereget
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.