> 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