> 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

Reply via email to