🌀

[Swift] structでも再帰する

2021/04/20に公開

Swiftでは以下のような型をかけません。サイズが決まらないからです。

struct Node<T> {
    let value: T
    let previous: Node<T>?
}

しかしだからといってclassにするのは気乗りしない場合があります。参照型は色々と扱いが厄介です。そこでstructでもどうにか再帰させてみましょう。

まずはproperty wrapperを用意します。これがミソで、再帰的enumとなっています。

@propertyWrapper
enum Recursive<T> {
    indirect case value(T)
    init(_ wrappedValue: T){
        self = .value(wrappedValue)
    }
    var wrappedValue: T {
        get {
            switch self{
            case let .value(value): return value
            }
        }
        set {
            self = .value(newValue)
        }
    }
}

これを使うとこんな風に書けます。

struct Node<T> {
    let value: T
    @Recursive private(set) var previous: Node<T>?
    
    func getPreviousValue() -> T? {
        previous?.value
    }
}

property wrapperがvarにしか使えない都合上、今回はアクセスをprivate(set)にしておきました。少なくとも外からは変更できなくなっています。

property wrapperを使ったおかげで、表面上他のプロパティと同様にpreviousNode<T>?の値にアクセスすることができています。また、enumを利用したおかげで値を変更しようとするとmutatingの利用を強制されます。classでproperty wrapperを実装した場合はこうはいきません(letにできるのが一番良いのですが)。

@propertyWrapper
class Recursive<T> {
    private var value: T
    init(_ wrappedValue: T){
        self.value = wrappedValue
    }
    var wrappedValue: T {
        get {
            return self.value
        }
        set {
            self.value = newValue
        }
    }
}

struct Node<T> {
    let value: T
    @Recursive private(set) var previous: Node<T>?

    func getPreviousValue() -> T? {
        previous = self           //mutatingでなくても代入できてしまう。
        return previous?.value
    }
}

もちろんproperty wrapperとしては使わず、直接包んで用いるという手もあります。これならletで宣言できます。若干型名部分が長くなり、利用が面倒になるのがイマイチですが、こちらでも十分スッキリしているとは思います。

struct Node<T> {
    let value: T
    let previous: Recursive<Node<T>?>
    
    func getPreviousValue() -> T? {
        previous.wrappedValue?.value
    }
}

Discussion