[swift-evolution] [Draft] Throwing Properties and Subscripts

Brent Royal-Gordon brent at architechies.com
Mon Mar 14 18:59:34 CDT 2016

> Do you have an imagined use for throwing getters?

In the proposal, I cite a pair of framework methods that I think would be better modeled as a throwing subscript:

	class NSURL {
		func getResourceValue(_ value: AutoreleasingUnsafeMutablePointer<AnyObject?>, forKey key: String) throws
		func setResourceValue(_ value: AnyObject?, forKey key: String) throws

Here's another, much less matter-of-opinion usage:

  	// Numeric types, Bool, String, JSONValue?, [JSONValue], and [String: JSONValue] conform.
	protocol JSONValue { ... }
	protocol JSONRepresentable {
		associatedtype JSONRepresentation: JSONValue
		init(json: JSONRepresentation) throws
		var json: JSONRepresentation { get throws }
	struct User: JSONRepresentable {
		var json: [String: JSONValue]
		var friends: [User] {
			get throws {
				guard let friendsJSON = json["friends"] as? [User.JSONRepresentation] else {
					throw UserError.InvalidFriends
				return friendsJSON.map { try User(json: $0) } 
			set throws {
				json["friends"] = newValue.map { try $0.json }

As long as we're doing computation in getters, it will make sense for that computation to raise errors. I don't think we can get around the need for `get throws`.

> Allowing the getter or setter to throw independently greatly complicates the abstract model for properties. While we don't have much in the way of abstraction tools over properties yet, it would be important to consider the impact this might have on "lens" functions that can perform mutable projections. Right now, you can more or less categorize mutable properties into two groups:
> - "reference-like", projecting mutable storage indirectly through a reference, which you could think of as having a type (Base) -> inout Property. This includes not only class properties but `nonmutating` struct properties like `UnsafeMutablePointer.memory`.
> - "value-like", projecting mutable storage that is part of a larger mutable value, which you could think of as having type (inout Base) -> inout Property. This includes most mutable struct properties that aren't explicitly `nonmutating`.

I think you may be overoptimistic here—I've tried to prototype lenses before (using protocols and classes) and I've always quickly ended up in combinatorial explosion territory even when merely modeling existing behavior. First of all, mutability is uglier than you imply:

- There's actually a third setter category: read-only. 
- The getter and setter can be *independently* mutating—Swift is happy to accept `mutating get nonmutating set` (although I can't imagine why you would need it).

So that's already six, not two, categories.

Another complication comes from the type of the property in the lens's view. You need Any-typed lenses for KVC-style metaprogramming, but you also want type-specialized lenses for greater safety where you have stronger type guarantees. And yet their setters are different: Any setters need to be able to signal that they couldn't downcast to the concrete type of the property you were mutating. (This problem can actually go away if you have throwing setters, though—an Any lens just has to make nonthrowing setters into throwing ones!)

(For added fun: you can't model the relationship between an Any lens and a specialized lens purely in protocols, because that would require support for higher-kinded types.)

So if you want to model the full richness of property semantics through their lenses, the lens system will inevitably be complicated. If you're willing to give up some fidelity when you convert to lenses, well, you can give up fidelity on throwing semantics too, and have the lens throw if either accessor throws.

> If properties can throw, we'd want to be able to represent that in the type system for projection functions. If there are four different ways a property can throw (no throws, only reads can throw, only writes can throw, or both reads and writes can throw), that gets really complicated.

Nevertheless, this might be a good enough reason to invoke the escape hatch I left in the Alternatives Considered section:

	Calling a setter often implicitly involves calling a getter, so it may 
	make sense to require the setter to be at least as throwing as the 
	getter. Absent feedback to this effect from implementors, however, my 
	instinct is to leave them independent…

The way I see it, we have three options for the throwing variants we might allow:

- Independent: Any combination
- Dependent: Getter can't throw unless setter can (no "get throws set")
- Setter only: Getter can't throw (no "get throws set" or "get throws set throws")

We also might want to look at that weird nonmutating case, so there are two options there:

- Independent: Any combination
- Dependent: Getter can't mutate unless setter can (no "nonmutating get mutating set")

(If we really wanted to, we could probably make the `mutating` property setter-only; things like `lazy` would just have to use a reference-typed box. That would be a pretty violent change to the language, though.)

That's a lot of possible semantics, so I wrote a script to help me understand what each policy would do. Here's the abridged results:

	20 variants for independent mutating, independent throws
	16 variants for independent mutating, dependent throws
	16 variants for dependent mutating, independent throws
	13 variants for dependent mutating, dependent throws
	12 variants for independent mutating, setter-only throws
	10 variants for dependent mutating, setter-only throws

(Full results, plus script: <https://gist.github.com/brentdax/97e3dfe7af208da51a1a>. By the way, without any `throws` support at all, independent mutating requires 6 variants and dependent mutating requires 5.)

My main conclusion is that limiting `throws` to setters only doesn't actually gain us much over limiting both `throws` and `mutating` to the "sensible" combinations.

And again, this only applies if we want lenses to model all property semantics with complete fidelity. We could offer fewer lenses if we're willing to accept clunkier interfaces. Heck, we could offer a single lens type if we're okay with dynamic safety:

	// Semantics would be equivalent to:
	struct Lens<Instance, Value> {
		// Can throw only errors thrown by the property.
		func value(for instance: inout Instance) throws -> Value

		// Can throw `SetterInaccessible` or errors thrown by the property.
		func updateValue<T>(for instance: inout Instance, mutator: (inout Value) -> T) throws -> T
		// Can throw `RequiresMutating` or errors thrown by the property.
		func value(for instance: Instance) throws -> Value
		// Can throw `SetterInaccessible`, `RequiresMutating`, or errors thrown by the property.
		func updateValue<T>(for instance: Instance, mutator: (inout Value) -> T) throws -> T
	extension Lens {
		// `updateValue` methods can also throw `InvalidValueType`.
		init<ConcreteValue: Value>(downcasting: Lens<Instance, ConcreteValue>)
	enum LensError: Error {
		case InvalidValueType (expectedType: Any.Type, foundType: Any.Type)
		case SetterInaccessible
		case RequiresMutating

Brent Royal-Gordon

More information about the swift-evolution mailing list