>> 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.
Debatable, sure.
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. That doesn't man we are
*obliged* to add features to support every weird setter variation—for instance,
I doubt we're ever going to accommodate UIKit's love of `setFoo(_:animated:)`
methods—but I think we ought to lean towards making properties and subscripts
as powerful as methods when we can reasonably do so.
>> - 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.
Yes, I mean a read-only property (no setter). Currently these aren't exposed at
all on the type, even though we could provide read-only access.
So I take it what you're proposing is that this:
struct Foo {
func method() { ... }
mutating func mutatingMethod() { ... }
var readOnlyProperty: Int { get { ... } }
var readOnlyMutatingProperty: Int { mutating get { ... } }
var readWriteProperty: Int { get { ... } set { ... } }
var readWriteNonmutatingProperty: Int { get { ... } nonmutating
set { ... } }
}
Also has these members?
extension Foo {
// These argument lists might be combined in the future
static func method(self: Foo) -> () -> Void
static func mutatingMethod(self: inout Foo) -> () -> Void
static func readOnlyProperty(self: Foo) -> Int
static func readOnlyMutatingProperty(self: inout Foo) -> Int
static func readWriteProperty(self: inout Foo) -> inout Int
static func readWriteNonmutatingProperty(self: Foo) -> inout Int
}
(Hmm. There might be room for a `reinout` or `inout(set)` along the lines of
`rethrows`:
static func readWriteProperty(self: reinout Foo) -> inout Int
That would mean the `self` parameter is inout only if the return value is
treated as inout.)
>> - 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.
That can work. But like I said, you could do the same thing with `throws`.
> `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.
That would interfere with the rather elegant mutating-get-to-copy-on-write
pattern: <https://developer.apple.com/videos/play/wwdc2015/414/?time=2044>. I
suppose a mutating method would work the same way as long as you were backing
the instance with a reference type, though.
>> 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.
Sorry, that probably wasn't as clear as it should be.
I would like to eventually have a way to dynamically look up and use lenses by
property name. I think this could serve as a replacement for KVC. So, for
instance, the `Foo` type I showed previously might have dictionaries on it
equivalent to these:
extension Foo {
static var readableProperties: [String: (inout Foo) -> inout
Any] = [
"readWriteProperty": { $0.readWriteProperty },
"readWriteNonmutatingProperty": { ... },
"readOnlyProperty": { ... },
"readOnlyMutatingProperty": { ... }
]
static var writableProperties: [String: (inout Foo) -> inout
Any] = [
"readWriteProperty": {
get { return $0.readWriteProperty }
set { $0.readWriteProperty = newValue as! Int }
},
"readWriteNonmutatingProperty": { ... }
]
}
Then you could dynamically look up and use a lens:
for (propertyName, value) in configurationDictionary {
let property = Foo.writableProperties[propertyName]!
property(&foo) = value
}
(These dictionaries would not be opted into with a protocol; they would be
synthesized at a given use site, and would contain all properties visible at
that site. That would allow you to pass a dictionary of lenses to external
serialization code, essentially delegating your access to those properties.)
But note the forced cast in the `readWriteProperty` setter: if the Any assigned
into it turns out to be of the wrong type, the program will crash. That's not
good. So the setter has to be able to signal its failure, and the only way I
can imagine to do that would be to have it throw an exception:
static var writableProperties: [String: (inout Foo) throws ->
inout Any] = [
"readWriteProperty": {
get { return $0.readWriteProperty }
set {
guard let newValue = newValue as? Int
else {
throw PropertyError.InvalidType
}
$0.readWriteProperty = newValue
}
},
…
for (propertyName, value) in configurationDictionary {
let property = Foo.writableProperties[propertyName]!
try property(&foo) = value
}
Hence, non-throwing setters would have to become throwing setters when you
erased the property's concrete type.
>> (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.
Exactly.
(Although in theory, we could add a `throws(set)` syntax on inout-returning
functions, indicating that the getter never throws but the setter does, much
like the `reinout` I mentioned earlier.)
--
Brent Royal-Gordon
Architechies
_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution