I don't think we get to define "macro" however we want. There is significant prior art in other languages (Scheme, Rust, Scala, ... even C) for what a "macro system" should accomplish. Property delegates aren't doing a fraction of that, and we shouldn't pretend that do.
Existing precedent is a great point in favor of the #Foo syntax for using property delegates.
Oh, that doesn't work for technical reasons (the autoclosure can't return an inout, so you would be unable to forward a setter). Something more like the keypath formulation that's been discussed elsewhere in this thread gets closer to solving this problem (but has problems with exclusivity---no silver bullet here).
I think the "expose it as a separate decl" part is what makes this feel like a macro expansion rather than a magical language feature.
I'll mention that I'm concerned about using the #Foo syntax because it makes it harder to add other things like #if, #sourceLocation, #selector, and #available in the future. I'd rather keep prefix # as something for explicit compiler features that don't deserve full-on keywords, rather than have it sometimes be that and sometimes take an "argument".
To be fair that did not stop the maybe minor maybe not change of meaning of do (do - while â> repeat - while as do was used for do - catch instead of try - catch). We were able to move past it and we could for the name macro perhaps.
Changing a the spelling of a keyword is pretty different from changing the meaning of an entire concept. Macros are very similar across languages in purpose, while loops have a wide range of different spellings.
I really donât think property delegates are the right way to address encoding behaviors. Property delegates are primarily an abstraction of storage (access to storage, location of storage, etc). Encoding and decoding are not about storage but about an external representation of the value.
I am also not convinced that Equatable and Hashable should be defined in terms of the delegate rather than the value of the primary property. I see the point that there may be an opportunity for an optimized implementation in a delegate but am unsure of the implications of this approach. I think in general I would find it pretty surprising if using a delegate for a property resulted in a change to the semantics of its conformance to Equatable or Hashable.
First of all, your CustomStorage<T: Hashable> example already works with the proposal as written. It's fine to have requirements on the type parameter of a delegate type.
The MyStorage example could be made to work by loosening the rules, but the use case feels contrived. Is this really a delegate that cannot be abstracted in any meaningful way? Is it worth having two ways to spell property delegates to cover such a use case?
Neither of those declarations above are better than what is in the proposal:
var property_as_proposed: T by DelayedMutable
Your first (property_1) hides the type of the property (T) within the delegate type. That's hiding the most important part of property_1 from the user.
Your second (property_2) duplicates the type T, and not in a way that brings any additional clarity to the code.
It is possible, yes, but it makes the model more complex, and all of the examples given so far have been theoretical or contrived. I don't feel that the complexity is warranted at this stage.
Itâs true that I didnât commented to it directly, I read it and I respect your view point, but I think you missed my point here. With these variations Iâm not trying to make things complex but saying that these must be valid as this would be already regular Swift if we had the by syntax. Itâs not a question about clarity or duplication here but about consistency in the language. property_1 is basically what you have in your proposal as var property by Lazy = 17 but with explicit type instead of inference through init(initialValue:) and I didnât provide any default value. From the original proposal example I think this form should imply as valid.
In fact it was you who told me that you regret that Swift allow us to omit the generic type parameter list like for example when extending Dictionary, and now weâre again in the same situation and trying to do the exact same thing.
Donât get me wrong here ;) if you want to omit the single type parameter, fine, but the explicit form must be possible if ever we need it for disambiguation, as the compiler not always doing what we expect.
Okay thatâs fair. Another possible future direction then?
I'm not sure if that would be useful for the community but I found that maybe this pitch could be used to resolve one of the features that I would personally find sometimes handy.
Property delegates could be used to decide on the runtime who should own an object instead of explicitly decide it during type definition.
The problem is how we could pass more information besides the initialValue.
@propertyDelegate
class AdjustableOwner<Value: AnyObject> {
enum OwnageType {
case strong
case weak
case unowned
}
private let type: OwnageType
private var _strongValue: Value
private weak var _weakValue: Value!
private unowned var _unownedValue: Value!
init(initialValue: Value, ownageType: OwnageType = .strong) {
self.type = ownageType
switch ownageType {
case .strong:
_strongValue = initialValue
case .weak:
_weakValue = initialValue
case .unowned:
_unownedValue = initialValue
}
}
var value: Value {
get {
switch type {
case .strong:
return _strongValue
case .weak:
return _weakValue
case .unowned:
return _unownedValue
}
}
set {
switch type {
case .strong:
_strongValue = newValue
case .weak:
_weakValue = newValue
case .unowned:
_unownedValue = newValue
}
}
}
class A { }
class B {
var a: A by AdjustableOwner
init(a: AdjustableOwner<A>) {
self.$a = a
}
}
let a = A()
let b = B(a: a) // strong by default
let b = B(a: a|strong)
let b = B(a: a|weak)
let b = B(a: a|unowned)
Overall the way I see this is that you are introduction two new concepts to swift.
Direct store backing $somepropety access using the $
I love the idea of getting access to the store backing of a property using $.
Maybe I missed it but am I going to be able to access this with any property?
The only other place where we using $ is for tuples $0, $1 etc. Could I use $self to reference the tuples itself? Would $self be valid?
Some form of attributes specifically for properties using by
I am not to excited about using by because I think it will conflict with custom tributes in the future. Would you consider introducing custom attributes first even if they are only able to define Property delegates at first?
I'm not sure which of my regrets you're referring to. I'm very happy with our decision to have the generic type parameters in scope in an extension, e.g.,
extension Dictionary {
// can refer to Key and Value here, but I'm happy with that
}
It makes it clear that the names of generic parameters are part of the API, which I feel contributes to better API design.
I'm happy with the ability to omit generic parameters when they're inferred, e.g.,
let d: Dictionary = [1: "Hello"] // infers Key=Int, Value=String, and I'm happy with that
Property delegates is effectively using that feature for the var x by Lazy = 17 case.
One thing I regret is being able to refer to Dictionary without generic parameters within the Dictionary definition or its extensions even when there is no inference. It collides with inference, and SE-0068's use of Self is so much smarter. I think my (bad) decision to allow the following may have predated the existence of Self in the language. I hope it did, because then I'd have some historical cover for my mistake ;)
extension Dictionary {
func equals(_ other: Dictionary) -> Bool { // same as Dictionary<Key, Value>, which was a Bad Idea
}
}
With the current proposal, you don't need disambiguation because there is ambiguity: the original property type (e.g., the Int in var x: Int by Lazy) is plugged in as the generic argument to the delegate type.
Yet still I don't understand the resistance of not allowing us to type out the full property delegate type. The proposal explicitly says that we can omit it, so people who prefer that can just omit the parameter list.
The only other case we have in the language where there is a backing stored property that's implicitly generated by the compiler is lazy. I suppose we could expose it (it has optional type) this way, but extending the feature set of the built-in features within the property delegates proposal seems a bit strange.
I couple of people have mentioned this, and it's worth investigating because it could pull together a couple of desired features into a single conceptual framework. I'm going to take a shot at implementing the @Lazy var x = 17, @UserDefaults(key: "key", default: true) var isSomething: Bool syntax implied by the custom-attributes approach before I make any conclusions here.
I'm resisting because I don't want to introduce two ways to do the same thing without solid use cases for the second mechanism, because it complicates the description of the feature.
Okay that's a fair argument. I want to ask you or in general the core team if you/they would think that it would be reasonable to allow non-generic or generic types with more type parameters as property delegates in the future?
Speaking personally, it's a possible avenue for future extension, assume the design is good and supported by enough compelling use cases to motivate extension to the language.
I have one other question that I'd like to ask. Assuming the proposal went though the review and is accepted. Will the stdlib extended with a set of (useful/common) property delegates (which the stdlib itself make use of)? In separate proposal of course.