Open3

dynamicMemberLookupと仲良くなる

kotasankotasan

https://developer.apple.com/documentation/foundation/attributedynamiclookup

自身が保持しないプロパティをあたかも保持するかのように振る舞える機能(と理解した)


以下のような構造体を考えてみる.

struct Wrapper<Wrapped> {
    var wrapped: Wrapped

    init(wrapped: Wrapped) {
        self.wrapped = wrapped
    }
}

こちらは単にWrapped型のプロパティをwrapするだけ.

次はこれでString型の変数をwrapしてみよう

struct Wrapper<Wrapped> {
    var wrapped: Wrapped

    init(wrapped: Wrapped) {
        self.wrapped = wrapped
    }
}

+ let string = "Hello, World!"
+ let wrapper = Wrapper(wrapped: string)

この時,文字数を数えたいとする.
変数stringに対しては,String.count methodを呼べば良い.

string.count // 13

変数wrapperに対しては,次のようにすると同様に文字数を数えられる.

wrapper.wrapped.count // 13

ただし上の書き方ではちょっと冗長に感じる.ここで次の記法でかけるようにしたい

wrapper.count

それを実現するのがdynamicMemberLookup

+ @dynamicMemberLookup
struct Wrapper<Wrapped> {
    var wrapped: Wrapped

    init(wrapped: Wrapped) {
        self.wrapped = wrapped
    }

+    subscript<T>(dynamicMember keyPath: KeyPath<Wrapped, T>) -> T {
+        get {
+            wrapped[keyPath: keyPath]
+        }
+    }
}

let string = "Hello, World!"
let wrapper = Wrapper(wrapped: string)

string.count
wrapper.wrapped.count
+ wrapper.count

@dynamicMemberLookupattributesの1つ.
くだけていうと,この文脈ではWrapper型がdynamicMemberLookupの機能を有していますよ,とコンパイラーに教えてあげるための目印.(というふうに理解している)

subscriptWrapped型(この文脈ではString型)を基点とするKeyPathを引数として受け取る.


これでwrapper.countと簡潔にWrapped型が持たないString.countを簡潔に呼び出せるようになった.いわばwrapper: Wrappedwrapper: Stringのように振る舞っているわけである.

kotasankotasan

先ほどはdynamicMemberの読み取りができたので,次にdynamicMemberの書き換えを試みたい.
wrapされるオブジェクトをもう少し複雑にして考えてみよう.

@dynamicMemberLookup
struct Wrapper<Wrapped> {
    var wrapped: Wrapped

    init(wrapped: Wrapped) {
        self.wrapped = wrapped
    }

    subscript<T>(dynamicMember keyPath: KeyPath<Wrapped, T>) -> T {
        get {
            wrapped[keyPath: keyPath]
        }
    }
}

struct Car {
    var name: String
    var maxSpeed: Int
}

var wrappedCar = Wrapper(wrapped: Car(name: "Toyota", maxSpeed: 200))
wrappedCar.name // "Toyota"
wrappedCar.maxSpeed // 200

ここでwrappedCar.nameを書き換えたくなった.

wrappedCar.name = "Honda" // Cannot assign to property: 'wrappedCar' is immutable

このままではできない.なぜならgetを持つsubscriptのみ用意しているから.そこで次のsubscriptに置き換える.

@dynamicMemberLookup
struct Wrapper<Wrapped> {
    var wrapped: Wrapped

    init(wrapped: Wrapped) {
        self.wrapped = wrapped
    }

-    subscript<T>(dynamicMember keyPath: KeyPath<Wrapped, T>) -> T {
-        get {
-            wrapped[keyPath: keyPath]
-        }
-    }
+    subscript<T>(dynamicMember keyPath: WritableKeyPath<Wrapped, T>) -> T {
+        get {
+            wrapped[keyPath: keyPath]
+        }
+        set {
+            wrapped[keyPath: keyPath] = newValue
+        }
+    }
}

こうすることで,次のように書き換えも可能になる.

var wrappedCar = Wrapper(wrapped: Car(name: "Toyota", maxSpeed: 200))
wrappedCar.name // "Toyota"
wrappedCar.maxSpeed // 200

wrappedCar.name = "Honda"
wrappedCar.name // "Honda"
kotasankotasan

他にもreferencewritablekeypathを引数にとるsubscript
get節, set節以外に_read節, _modify節もsubscriptにかけるらしい.また今度調べよう.