[Swift] PropertyWrapper 手を動かして理解する(その1 wrappedValue)
[Swift] PropertyWrapper 手を動かして理解する(その2 wrappedValue の初期化)
[Swift] PropertyWrapper 手を動かして理解する(その3 $ もしくは projectedValue の理解)
# まだ用語がブレてます。推敲していくことで統一予定。
いうまでもなく、オリジナルは、こちら。
Sponsor Link
Introduction/導入
プロパティを実装するパターンは、いくつかあって、繰り返し利用される。特定の固定コードをコンパイラーに入れ込むよりは、ライブラリとして定義されたこれらのパターンを
一般化した”Property Wrapper”のメカニズムとして提供する方が良い。
2015-106 property behaviorsでの方向性とは反対のアプローチとなる。
いくつかの例は同じだが、この提案は、デザインがよりシンプルになり、ユーザーに理解しやすく、コンパイラの実装も大きな変更を要さない。
以下のセクションでは、この設計の本質的な相違点について議論している。
Motivation/動機
これまでに、lazy や @NSCopying のようなプロパティーに対してのいくつかの重要なパターンを言語でサポートしようとしてきた、
しかし、これらは、非常に狭い領域でのユーティリティだった。
たとえば、Swiftは lazy プロパティを言語の基本機能として備えている、なぜなら、遅延初期化は一般的でありプロパティを optional として外部提供してしまうことを避けるために必要だからである。このサポートなしには、同様の効果を得るためには、非常に多くの定型的なコードが必要になる。
struct Foo {
// lazy var foo = 1738
private var _foo: Int?
var foo: Int {
get {
if let value = _foo { return value }
let initialValue = 1738
_foo = initialValue
return initialValue
}
set {
_foo = newValue
}
}
}
lazy を言語に取り込むことで、いくつかの不利益もあります。言語とコンパイラを複雑にしますし、直交性が犠牲となりました。また、柔軟性も失われました。遅延初期化については、何種類かのバリエーションがありましたが、全てをサポートは、しませんでした。
遅延初期化以外に重要なプロパティのパターンがあります。「一度設定して以降変更不可」を「遅延」させておこなうマルチフェーズ初期化です。
class Foo {
let immediatelyInitialized = "foo"
var _initializedLater: String?
// We want initializedLater to present like a non-optional 'let' to user code;
// it can only be assigned once, and can't be accessed before being assigned.
var initializedLater: String {
get { return _initializedLater! }
set {
assert(_initializedLater == nil)
_initializedLater = newValue
}
}
}
ここでは、Implicitly-unwrapped optionls(IUO)を使うことで実現されていますが、optionalでないletを使うことに比べて、多くの安全性が失われています。
マルチフェーズ初期化をIUOを使って実装することは、不変性とnil-安全性を失ってしまいます。
アトリビュート @NSCopying を導入したことで、変数設定時のコピー作成に、NSCopying.copy()を使うこととなりました。実装パターンは、これと似たものになるかもしれません。
class Foo {
// @NSCopying var text: NSAttributedString
var _text: NSAttributedString
var text: NSAttributedString {
get { return _text }
set { _text = newValue.copy() as! NSAttributedString }
}
}
Proposed solution/提案
“Property wrapper”の導入を提案します。それを使うことで、プロパティ定義に置いて、どのwrapperを使って実装するかを宣言できます。この wrapper はアトリビュートとして記述されます。
@Lazy var foo = 1738
この行によりプロパティ foo は、以下の”Property wrapper” Lazy として記述された方法で実装されます。
@propertyWrapper
enum Lazy {
case uninitialized(() -> Value)
case initialized(Value)
init(wrappedValue: @autoclosure @escaping () -> Value) {
self = .uninitialized(wrappedValue)
}
var wrappedValue: Value {
mutating get {
switch self {
case .uninitialized(let initializer):
let value = initializer()
self = .initialized(value)
return value
case .initialized(let value):
return value
}
}
set {
self = .initialized(newValue)
}
}
}
“Property wrapper type” は、ラッパーとして使われるプロパティのストレージを提供します。
Wrapper タイプの wrapperdValue プロパティは、ラッパーの実際の実装になります。optionalの init(wrappedValue:)によって、Valueタイププロパティを初期化することが可能です。
@Lazy var foo = 1738
上記は、以下のようなコードになります。
private var _foo: Lazy = Lazy(wrappedValue: 1738)
var foo: Int {
get { return _foo.wrappedValue }
set { _foo.wrappedValue = newValue }
}
Prefix _ は、同期ストレージプロパティ名として意図的に使われていて、同期ストレージプロパティの名前を事前合意していることで、private なプロパティを問題なく定義できます。例えば、Lazy で初期値に戻すための reset(_:)を定義することもできます。
extension Lazy {
/// Reset the state back to "uninitialized" with a new,
/// possibly-different initial value to be computed on the next access.
mutating func reset(_ newValue: @autoclosure @escaping () -> Value) {
self = .uninitialized(newValue)
}
}
_foo.reset(42)
背後にあるプロパティを明示的に初期化することができます。
extension Lazy {
init(body: @escaping () -> Value) {
self = .uninitialized(body)
}
}
func createAString() -> String { ... }
@Lazy var bar: String // not initialized yet / 初期化されてない
_bar = Lazy(body: createAString)
“Property wrapper”のインスタンスは、名前の後にカッコとイニシャライザの引数を指定することで直接初期化できます。
上記のコードは、以下の1行の変数定義のコードと同値になります。
@Lazy(body: createAString) var bar: String
“Property wrapper”は、グローバル/ローカル もしくは、タイプスコープにかかわらず、使えます。それらプロパティは、監視アクサッサ(willSet/didSet)を持つことができますが、明示的にgetter/setterを書く必要はありません。
“Lazy property wrapper”は、初期化以外にほとんどAPIを持ちませんので、外部に公開する意味は少ないかもしれません。
しかし、”property wrapper”は、興味深いAPIをもつ要素との関係を記述することができます。例えば、名前によって定義されたデータベースフィールドを参照する property wrapper を使うかもしれません。
@propertyWrapper
public struct Field {
public let name: String
private var record: DatabaseRecord?
private var cachedValue: Value?
public init(name: String) {
self.name = name
}
public func configure(record: DatabaseRecord) {
self.record = record
}
public var wrappedValue: Value {
mutating get {
if cachedValue == nil { fetch() }
return cachedValue!
}
set {
cachedValue = newValue
}
}
public func flush() {
if let value = cachedValue {
record!.flush(fieldName: name, value)
}
}
public mutating func fetch() {
cachedValue = record!.fetch(fieldName: name, type: Value.self)
}
}
モデルを上記の Field property wrapper を使って定義できます。
public struct Person: DatabaseModel {
@Field(name: "first_name") public var firstName: String
@Field(name: "last_name") public var lastName: String
@Field(name: "date_of_birth") public var birthdate: Date
}
Field そのものが、Person を使うユーザーにとって重要なAPIになっています。使うことで、値をflushしたり、新しい値をfetchしたり、データベースの対応するフィールドの名前を取得することもできます。しかし、アンダースコア付きの変数(_firstName, _lastName, _birthdate)は、privateであり、直接操作することはできません。
APIとして使えるようにするために、property wrapper “Field”は、”projection”を提供し、それ経由でデータベースとのフィールドの関係性を操作できます。
“Projection” プロパティは、前置詞として、”$”を使います。ですので、firstName プロパティは、 $firstName として使われ、firstName が参照可能な場所では、$firstName も参照可能です。”property wrapper”は、projetionを、projectedValueを定義することにより提供します。
@propertyWrapper
public struct Field {
// ... API as before ...
public var projectedValue: Self {
get { self }
set { self = newValue }
}
}
projectedValue が存在する場合は、projection 変数が、projectedValueをwrappするプロジェクション変数が作られます。例えば、以下のプロパティ
@Field(name: "first_name") public var firstName: String
は、以下のように展開されます。
private var _firstName: Field = Field(name: "first_name")
public var firstName: String {
get { _firstName.wrappedValue }
set { _firstName.wrappedValue = newValue }
}
public var $firstName: Field {
get { _firstName.projectedValue }
set { _firstName.projectedValue = newValue }
}
このように展開されることによって、”property”と”projection”の両方を操作することができるようになります。
somePerson.firstName = "Taylor"
$somePerson.flush()
Examples/例
詳細設計について説明する前に、いくつかの wrapper の例を提示する。
Delayed Initialization/遅延初期化
“Property wrapper”は、遅延初期化としてつられ、そこでは、コンパイル時にではなくより動的に明確な初期化ルールが強制されます。このことで、マルチフェーズ初期化の際に、IUO(Implicitly-unwrapped option)を避けることができます。変更可能(再設定可能なvarを使って)・変更不可(再設定できないletを使って)のいずれの変数も定義することができます。
@propertyWrapper
struct DelayedMutable {
private var _value: Value? = nil
var wrappedValue: Value {
get {
guard let value = _value else {
fatalError("property accessed before being initialized")
}
return value
}
set {
_value = newValue
}
}
/// "Reset" the wrapper so it can be initialized again.
mutating func reset() {
_value = nil
}
}
@propertyWrapper
struct DelayedImmutable {
private var _value: Value? = nil
var wrappedValue: Value {
get {
guard let value = _value else {
fatalError("property accessed before being initialized")
}
return value
}
// Perform an initialization, trapping if the
// value is already initialized.
set {
if _value != nil {
fatalError("property initialized twice")
}
_value = newValue
}
}
}
上記の定義を使うことで、マルチフェーズ初期化が可能となります。
class Foo {
@DelayedImmutable var x: Int
init() {
// We don't know "x" yet, and we don't have to set it
}
func initializeX(x: Int) {
self.x = x // Will crash if 'self.x' is already initialized
}
func getX() -> Int {
return x // Will crash if 'self.x' wasn't initialized
}
}
NSCopying
多くのCocoaのクラスでは、value-likeなオブジェクトを使っていて、それは、明示的なコピーを必要とします。Swiftでは、@NSCopying アトリビュートを提供していて、Objective-C の @property(copy) のような動作をさせることが可能となっています。プロパティにセットされたときに、新しい Object で copy method が呼ばれます。この振る舞いを wrapper にすることができます。
@propertyWrapper
struct Copying {
private var _value: Value
init(wrappedValue value: Value) {
// Copy the value on initialization.
self._value = value.copy() as! Value
}
var wrappedValue: Value {
get { return _value }
set {
// Copy the value on reassignment.
_value = newValue.copy() as! Value
}
}
}
この実装は、SE-0153で説明されている問題を解決します。copy() を init(wrappedValue:) の外側で使うことで、意味的には、SE-0153 の解決策となります。
(訳注:SE-0153ざっくり説明:イニシャライザの中で、setter経由でアクセスしないのがベストプラクティス)
Atomic/一連操作
一連操作(load, store, 増減, 比較して変更)は Swift に一般的に要求される機能である。
それら機能を実装することは、Compilerや標準ライブラリを魔法のようにしてしまうが、インターフェイス自身は、property wrapper として提供することができる。
@propertyWrapper
struct Atomic {
private var _value: Value
init(wrappedValue: Value) {
self._value = wrappedValue
}
var wrappedValue: Value {
get { return load() }
set { store(newValue: newValue) }
}
func load(order: MemoryOrder = .relaxed) { ... }
mutating func store(newValue: Value, order: MemoryOrder = .relaxed) { ... }
mutating func increment() { ... }
mutating func decrement() { ... }
}
extension Atomic where Value: Equatable {
mutating func compareAndExchange(oldValue: Value, newValue: Value, order: MemoryOrder = .relaxed) -> Bool {
...
}
}
enum MemoryOrder {
case relaxed, consume, acquire, release, acquireRelease, sequentiallyConsistent
};
以下は、Atomic の単純なユースケース。Atomic タイプを使うことで、ローレベル操作を組み合わせることは非常に一般的で、そこでは、シンプルな操作を使いますが、(メモリへのアクセス順番のような)ここの意味を考える必要があります。Property と 同キストレージプロパティは、よく使われます。
@Atomic var counter: Int
if thingHappened {
_counter.increment()
}
print(counter)
@Atomic var initializedOnce: Int?
if initializedOnce == nil {
let newValue: Int = /*computeNewValue*/
if !_initializedOnce.compareAndExchange(oldValue: nil, newValue: newValue) {
// okay, someone else initialized it. clean up if needed
}
}
print(initializedOnce)
Thread-specific storage/Thread 向け Storage
Thread 向け Storage (pthreads ベース) も property wrapper で実装できます。(Daniel Delwood より)
@propertyWrapper
final class ThreadSpecific {
private var key = pthread_key_t()
private let initialValue: T
init(key: pthread_key_t, wrappedValue: T) {
self.key = key
self.initialValue = wrappedValue
}
init(wrappedValue: T) {
self.initialValue = wrappedValue
pthread_key_create(&key) {
// 'Any' erasure due to inability to capture 'self' or
$0.assumingMemoryBound(to: Any.self).deinitialize(count: 1)
$0.deallocate()
}
}
deinit {
fatalError("\(ThreadSpecific.self).deinit is unsafe and would leak")
}
private var box: UnsafeMutablePointer {
if let pointer = pthread_getspecific(key) {
return pointer.assumingMemoryBound(to: Any.self)
} else {
let pointer = UnsafeMutablePointer.allocate(capacity: 1)
pthread_setspecific(key, UnsafeRawPointer(pointer))
pointer.initialize(to: initialValue as Any)
return pointer
}
}
var wrappedValue: T {
get { return box.pointee as! T }
set (v) {
box.withMemoryRebound(to: T.self, capacity: 1) { $0.pointee = v }
}
}
}
User Defaults
Property wrapper は、プロパティを、string-key データに変換することに使うことができます。以下、user defaults(Harlan Haskins より)の例で、
そこでは、wrapper type にデータを展開するためのメカニズムを作っています。
@propertyWrapper
struct UserDefault {
let key: String
let defaultValue: T
var wrappedValue: T {
get {
return UserDefaults.standard.object(forKey: key) as? T ?? defaultValue
}
set {
UserDefaults.standard.set(newValue, forKey: key)
}
}
}
enum GlobalSettings {
@UserDefault(key: "FOO_FEATURE_ENABLED", defaultValue: false)
static var isFooFeatureEnabled: Bool
@UserDefault(key: "BAR_FEATURE_ENABLED", defaultValue: false)
static var isBarFeatureEnabled: Bool
}
Copy-on-write
Ref/Box
“Clamping” a value within bounds
Property wrapper types in the wild
Composition of property wrappers/ property wrapper の合成
property に複数の property wrapper が記述されたときには、wrapper は、まとめられ、いずれも効果を持ちます。例えば、以下は DelayedMutable と Copying の合成です。
@DelayedMutable @Copying var path: UIBezierPath
初期化を後にすることができるプロパティで、値をセットしたときには、NSCopying の copy を使ってコピーが実行されるプロパティになります。
合成は、後ろにある wrapper type が前にある wrapper type の内側になるように階層化されて実装されます。もっとも内側にあるタイプは、オリジナルのプロパティタイプとなります。例えば、上記の例では、DelayedMutable<Copying<UIBezierPath>>というタイプになります。path へのgetter/setter は、2つのレベルの.wrappedValueを使うことになります。
注記:この設計では、property wrapper の合成は、可換ではありません。なぜなら、順番によって影響が異なるからです。
@DelayedMutable @Copying var path1: UIBezierPath // _path1 has type DelayedMutable>
@Copying @DelayedMutable var path2: UIBezierPath // error: _path2 has ill-formed type Copying>
このケースでは、タイプチェックにより、2番目の順番は使えません、なぜなら DelayedMutable は NSCopying に準拠していないからです。
いつでもこのようなことにできるわけではありません。意味的におかしいだけであるならば、タイプチェックによって検出できないかもしれません。
この合成アプローチに対する別案は、”Alternatives considered”で説明されています。
Detailed design/詳細設計
Property wrapper types/Property wrapper types
“property wrapper type” は、Property wrapper として使えるタイプです。”property wrapper type”への基本的な要件は以下の2つです。
1. “Property wrapper type”は、アトリビュート @propertyWrapper と一緒に定義されなければいけません。アトリビュートは、property wrapper type として使われることを示し、コンパイラーに様々なルールをチェックするタイミングを与えます。
2. “Property wrapper type”は、wrappedValue という名前のプロパティを持たなければいけません。そのアクセスレベルは、タイプ自身のものと同じです。これは、wrap された内部の値にアクセスするときにコンパイラによって使われます。
Initialization of synthesized storage properties/同期ストレージプロパティの初期化
Property wrapper を導入することで、(setter/getterを持つ)計算プロパティと wrap されるタイプを持つ格納型プロパティを持つことになります。格納型プロパティは、以下の3つの方法のうちの1つで初期化することができます。
1. オリジナルのプロパティのタイプのイニシャライザ(例:@Lazy var foo: Int のケースでのIntにあたり、init(wrappedValue:)を使います)そのイニシャライザは、1つのwrappedValue プロパティと同型のパラメータを受け取るはずです。アクセスレベルは、property wrapper type と同じです。init(wrappedValue:) が存在するならば、プロパティ定義で使うことができます。以下は例です。
@Lazy var foo = 17
// ... implemented as
private var _foo: Lazy = Lazy(wrappedValue: 17)
var foo: Int { /* access via _foo.wrappedValue as described above */ }
複数の合成された property wrapper の場合は、その全てが、init(wrappedValue:)を用意する必要があり、最終的なイニシャライザは、全てのレベルの呼び出しを wrap します。
@Lazy @Copying var path = UIBezierPath()
// ... implemented as
private var _path: Lazy> = .init(wrappedValue: .init(wrappedValue: UIBezierPath()))
var path: UIBezierPath { /* access via _path.wrappedValue.wrappedValue as described above */ }
2. property wrapper type の値を使って初期化, property wrapper typeをイニシャライザの引数後に配置するということ
var addressOfInt: UnsafePointer = ...
@UnsafeMutablePointer(mutating: addressOfInt)
var someInt: Int
// ... implemented as
private var _someInt: UnsafeMutablePointer = UnsafeMutablePointer(mutating: addressOfInt)
var someInt: Int { /* access via _someInt.wrappedValue */ }
複数の合成された property wrapper の場合は、一番外側の wrapper だけが、イニシャライザを持つかもしれません。
3. イニシャライザがなく、property wrapper type が引数のないイニシャライザ(init())を提供するときは、wrapper type の init() が格納型プロパティを初期化するために呼ばれます。
@DelayedMutable var x: Int
// ... implemented as
private var _x: DelayedMutable = DelayedMutable()
var x: Int { /* access via _x.wrappedValue */ }
複数の合成された property wrapper の場合は、一番外側の wrapper だけが、イニシャライザ( init() )を持つ必要があります。
Type inference with property wrappers / property wrapper でのタイプ推論
最初のプロパティラッパーが generic である時は、その generic 情報は、明示的に与えられるか、Swift がその変数定義から推測できなければいけない。その推測は以下のように行われる。
変数が、初期値 E を与えられている時は、A(wrapperValue: E, argsA…)の呼び出し結果のタイプと同じとなる、そこでは、A はアトリビュートのタイプであり、argsA は、そのアトリビュートに渡される引数である。以下は、例。
@Lazy var foo = 17
// type inference as in...
private var _foo: Lazy = Lazy(wrappedValue: 17)
// infers the type of '_foo' to be 'Lazy' _foo は Lazyと推測される
もし複数のラッパアトリビュートが存在するならば、呼び出しの引数は、ネストされる。以下は、例。
@A @B(name: "Hello") var bar = 42
// type inference as in ...
private var _bar = A(wrappedValue: B(wrappedValue: 42, name: "Hello"))
// infers the type of '_bar' to be 'A' _bar は、A> と推測される
上記ではなく、最初のラッパー亜トリビュートが、直接の初期化引数 E…を持つならば、一番外側のラッパータイプは、A(E…)と同じタイプになる、そこでは、A は、最初に書かれたアトリビュートのタイプとなる。最初に書かれたものの後にあるラッパアトリビュートは、直接のInitializerを持っていないかもしれない。以下は、例。
コード
@UnsafeMutablePointer(mutating: addressOfInt)
var someInt
// type inference as in...
private var _someInt: UnsafeMutablePointer = UnsafeMutablePointer.init(mutating: addressOfInt)
// infers the type of `_someInt` to be `UnsafeMutablePointer` _someInt は UnsafeMutablePointer と推測される
上記ではなく、初期化されずに、オリジナルのプロパティが、タイプ指定をもっているならば、wrappedValue プロパティのタイプは、オリジナルプロパティのタイプと同じとなる。以下は例。
コード
@propertyWrapper
struct Function {
var wrappedValue: (T) -> U? { ... }
}
@Function var f: (Int) -> Float? // infers T=Int, U=Float T は、Int, U は Float と推論される
@UnsafeMutablePointer(mutating: addressOfInt)
var someInt
// type inference as in...
private var _someInt: UnsafeMutablePointer = UnsafeMutablePointer.init(mutating: addressOfInt)
// infers the type of `_someInt` to be `UnsafeMutablePointer` _someInt は UnsafeMutablePointer と推測される
上記ではなく、初期化されずに、オリジナルのプロパティが、タイプ指定をもっているならば、wrappedValue プロパティのタイプは、オリジナルプロパティのタイプと同じとなる。以下は例。
@propertyWrapper
struct Function {
var wrappedValue: (T) -> U? { ... }
}
@Function var f: (Int) -> Float? // infers T=Int, U=Float T は、Int, U は Float と推論される
いずれの場合でも、最初のラッパタイプは、アトリビュートに記述された最初の指定となる。さらにいうと、2つ目以降のラッパアトリビュートについては、その前のラッパータイプのwrappedValue プロパティのタイプになる。最終的に、もし、タイプ指定があるならば、最後のラッパータイプのwrappedValue プロパティのタイプは、その指定と一致する。もしこれらのルールで推測できない、もしくは、不整合が発生するならば、その変数定義は正しくないということになる。以下は、例。
@Lazy var foo: Int // okay オーケー
@Lazy var bar: Double // error: Lazy.wrappedValue is of type Int, not Double :エラー Lazy.wrappedValue は Int であり、Double でないので不正
その推測プロセスは、オリジナルのタイプ指定が省略されていても、オリジナルプロパティのタイプ情報を提供する、もしくは、タイプ指定から省略された generic を提供する。以下は、例。
@propertyWrapper
struct StringDictionary {
var wrappedValue: [String: String]
}
@StringDictionary var d1. // infers Dictionary
@StringDictionary var d2: Dictionary // infers
Custom attributes
プロパティラッパーは、一種のカスタムアトリビュートであり、アトリビュートの文法は、Swift で定義される要素へと参照されるために使われる。
文法的には、プロパティラッパーは、以下のように記述される。
attribute ::= '@' type-identifier expr-paren?
Type-identifier は、プロパティラッパータイプを参照しなければいけなく、そこに generic を含むことができる。このことにより、アトリビュート名に修正を入れることができるようになる。以下は、例。
@Swift.Lazy var foo = 1742
Expr-paren は、(もし存在するならば)、ラッパーインスタンスの初期化のための引数となる。
このカスタムアトリビュートの定式化は、lerger proposal for custom attributes と齟齬がなく、そこでも上記と同様のカスタムアトリビュートのための文法が使われているだけでなく、アトリビュートとして使用できるためのタイプを定義する別の方法も言及されている。このやり方では、@propertyWrapper は、一種のカスタムアトリビュートであり、コンパイル時にのみや実行時のみに有効なカスタムアトリビュートも存在すると考えられる。
Mutability of properties with wrappers
一般に、プロパティは、getter と setter をもつプロパティラッパーを持つ。ただし、プロパティタイプの wrappedValue プロパティに setter がない時やアクセスできないときには、プロパティタイプの setter もないかもしれない。
プロパティラッパーの wrappedValue プロパティが、mutating であり、struct の一部であるならば、同期getter が mutating となる。
同様に、プロパティラッパーの wrappedValue プロパティが nonmutating であるか、プロパティラッパーが class であるならば、同期 setter は nonmutating となる。以下は、例。
@propertyWrapper
struct MutatingGetterWrapper {
var wrappedValue: Value {
mutating get { ... }
set { ... }
}
}
@propertyWrapper
struct NonmutatingSetterWrapper {
var wrappedValue: Value {
get { ... }
nonmutating set { ... }
}
}
@propertyWrapper
class ReferenceWrapper {
var wrappedValue: Value
}
struct Usewrappers {
// x's getter is mutating
// x's setter is mutating
@MutatingGetterWrapper var x: Int
// y's getter is nonmutating
// y's setter is nonmutating
@NonmutatingSetterWrapper var y: Int
// z's getter is nonmutating
// z's setter is nonmutating
@ReferenceWrapper var z: Int
}
訳注:とりあえず、ここまで
誤訳, Typo 等、お気づきの点ありましたら、教えてください。
残り
Out-of-line initialization of properties with wrappers
Memberwise initializers
Codable, Hashable, and Equatable synthesis
$ identifiers
Projections
Restrictions on the use of property wrappers
Impact on existing code
Backward compatibility
Alternatives considered
Composition
Composition via nested type lookup
using a formal protocol instead of @property wrapper
Kotlin-like by syntax
Alternative spellings for the $ projection property
The 2015-2016 property behaviors design
Future Directions
Finer-grained access control
Referencing the enclosing ‘self’ in a wrapper type
Delegating to an existing property
Revisions
Acknowledgements
Sponsor Link