> A mutable return has to be invariant. It'd be more sound to model that as 
> "exists T. (inout Foo) -> inout T", if our type system becomes powerful 
> enough to represent that, than to erase the return type, since you could then 
> conceivably still recover the T by opening the existential.

I'm trying to unpack this statement. I *think* what you're saying is that, 
since a given lens always returns the same type, the lens should itself be 
stored in an existential, rather than returning an existential, so you can 
discover the type it returns. In other words, instead of being "function from 
mutable Foo to mutable any", like this:

        inout Foo throws -> inout Any

They should be "any function from mutable Foo to some mutable type", which 
would be something along the lines of:

        Any<T in inout Foo throws -> inout T>

(Although that syntax is a catastrophe—best I could come up with, but still 
horrible. `T in` declares a generic type parameter, if it's not clear.) 

Is that accurate?

If so, it still seems like Swift ought to let you set from Any using the 
existential so you don't have to explicitly unwrap it all the time. That would 
provide something equivalent to this:

        extension<T> Any<inout Foo throws -> inout T> {
                // Inside the extension, T is bound to the return type, so we 
can open `self` by casting it with T.
                
                invoke(instance: inout Foo) throws -> inout Any {
                        get {
                                let concreteInvoke = self as inout Foo throws 
-> inout T
                                return try concreteInvoke(&instance) as Any
                        }
                        set {
                                let concreteInvoke = self as inout Foo throws 
-> inout T
                                guard let newValue = newValue as? T {
                                        throw InvalidTypeError
                                }
                                try concreteInvoke(&instance) = newValue
                        }
                }
        }

And it also seems like, if we decide not to take the generics system far enough 
to support this kind of existential, an implementation using type `inout Foo -> 
throws inout Any` is a good compromise. (Though we shouldn't rush to offer it 
if we do intend to support that kind of existential later on.)

In any case, however, this is digressing pretty far from the proposal itself.

* * *

If I recall correctly, Joe, your objections were only to throwing getters, and 
were as follows:

— Abstracting over the property might become more complicated if there are many 
possible combinations of throwabiity.

Is it enough to say that we can make a lens throw if either accessor can throw, 
as we've been discussing?

Do you think it doesn't make sense to support `get throws set` configurations? 
(Given that `get` and `set` are often combined into a single operation, I can 
understand why you would feel that way.) If so, do you think we should also 
consider (in a separate proposal) eliminating `mutating get nonmutating set` 
configurations?

— You think that code which can throw should generally be a method, not a 
getter.

I've already mentioned my opinion on the appropriateness of this:

> My intuition is that, if you have a pair of things which act as getter and 
> setter, Swift ought to permit you to treat them as a single member. If Swift 
> erects a barrier preventing you from doing that, that is a limitation of the 
> language and ought to be considered a negative.


I also have a couple of practical concerns.

I worry that, if Swift doesn't support throwing from getters, people will not 
respond by using methods; they will instead respond by handling errors in 
suboptimal ways, such as:

* Using `try!` or other trapping error handling mechanisms inside the getter, 
making errors impossible to handle gracefully.
* Returning optionals, which will throw away error information and permit the 
setter to accept meaningless `nil` values.
* Returning implicitly unwrapped optionals, which have the same disadvantages 
as optionals, in addition to undermining Swift's goal of forcing you to 
explicitly handle expected errors.
* Returning `Result`-style enums, which will make the property more difficult 
to use and permit the setter to accept meaningless error values.
* Abusing a future universal error mechanism, which would undermine Swift's 
goal of forcing you to explicitly handle anticipatable errors.
* Abusing exception mechanisms from other languages bridged into Swift, which 
would cause undefined behavior.

I also think that, in places where a protocol requirement might reasonably be 
fulfilled by either non-throwing storage or potentially-throwing computation, 
the absence of throwing getters might force you to favor one implementation or 
the other. For instance, consider the JSONRepresentable protocol I used in an 
earlier example:

        protocol JSONRepresentable {
                associatedtype JSONRepresentation: JSONValue
                
                init(json: JSONRepresentation) throws
                var json: JSONRepresentation { get throws }
        }

Some types are best implemented by storing the data in one property as 
(unpacked) JSON; others are best implemented by storing the data in separate 
properties and generating the JSON on demand. If getters can't throw, then 
`json` must either be a non-throwing getter (disadvantaging types which 
generate the JSON) or a throwing method (disadvantaging types which store the 
JSON).

Finally, I think that even if properties might not have strong enough use 
cases, subscripts will. For instance:

        enum XMLNode {
                // ...cases omitted...
                
                private var underlyingNode: NSXMLNode {
                        // Omitted because massive switch statements.
                        get { ... }
                        set { ... }
                }
                private var mutatingUnderlyingNode: NSXMLNode {
                        mutating get {
                                underlyingDocument = underlyingDocument.copy() 
as! NSXMLDocument
                                return underlyingDocument
                        }
                }
                
                subscript(xPath path: String) -> [XMLNode] {
                        get throws {
                                return try 
underlyingNode.nodesForXPath(path).map(XMLNode.init)
                        }
                        set throws {
                                let oldUnderlyingValue = try 
mutatingUnderlyingNode.nodesForXPath(path)

                                precondition(newValue.count == 
oldUnderlyingValue.count)
                                for (newNode, oldUnderlyingNode) in 
zip(newValue, oldUnderlyingValue) {
                                        newNode.supplant(oldUnderlyingNode)
                                }
                        }
                }
        }

And if you think that methods should replace throwing property getters, then 
surely once inout-returning functions are introduced, their getters will need 
to throw. At that point you'll be in a situation where most getters in Swift 
can throw, but there's a specific exception for property getters, arising out 
of a linguistic opinion rather than a general rule. That's the sort of strange 
inconsistency Swift is otherwise good at avoiding. (For instance, I was very 
pleased indeed to discover the `while let` loop last year.)

Even if you and like-minded programmers don't like them, I can't see any 
practical reason to forbid throwing getters. They don't appear to undermine any 
of the error handling system's goals, introduce confusing or inconsistent 
behavior, or severely complicate the compiler's job. They merely stretch the 
role of a property somewhat. And it's a pretty mild kind of stretching; I think 
throwing getters are less strange, for instance, than the generic constants 
Chris Lattner and others are interested in introducing.

I do understand why you and other developers might not want to use throwing 
getters, but I think that's ultimately a matter of style, not of correctness. 
Throwing getters seem like a design choice that reasonable people might make.

Even if you would not use throwing property getters, do you agree that they're 
conceptually well-formed and others with differing opinions might want to use 
them?

-- 
Brent Royal-Gordon
Architechies

_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to