Joe, was there an off-list email? I don't see your comments in my mail client or on Mailman, only quoted by Brent.
On Mar 14, 2016, at 7:59 PM, Brent Royal-Gordon via swift-evolution <[email protected]> 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`. > >> 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 > Architechies > > _______________________________________________ > swift-evolution mailing list > [email protected] > https://lists.swift.org/mailman/listinfo/swift-evolution _______________________________________________ swift-evolution mailing list [email protected] https://lists.swift.org/mailman/listinfo/swift-evolution
