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

Joe Groff jgroff at apple.com
Mon Mar 14 20:21:02 CDT 2016


> On Mar 14, 2016, at 4:59 PM, Brent Royal-Gordon <brent at architechies.com> wrote:
> 
>> 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`.

It's debatable whether this is a good use of property syntax. The standard library doesn't even use property syntax for things that might unconditionally fail due to programmer error.

>> 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.

How is that different from a nonmutating setter? Did you mean a read-only property? A read-only property is just a regular function, Base -> Property.

> - 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).

Fair point. From the point of view of the property abstraction, though, `mutating get nonmutating set` is erased to `mutating get mutating set`. That leaves three kinds of mutable property projection. `mutating get` itself is sufficiently weird and limited in utility, its use cases (IMO) better handled by value types holding onto a class instance for their lazy- or cache-like storage, that it might be worth jettisoning as well.

> 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!)

This sounds like something generics would better model than Any polymorphism, to carry the type parameter through the context you need polymorphism.

> (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.


True, you could say that if either part of the access can throw, then the entire property access is abstractly considered `throws`, and that errors are checked after get, after set, and for an `inout` access, when materializeForSet is called before the formal inout access (to catch get errors), and also after the completion callback is invoked (to catch set errors). That means you have to `try` every access to an abstracted property, but that's not the end of the world.

-Joe


More information about the swift-evolution mailing list