Быстрое запоминание / кеширование ленивой переменной в структуре

Я выпил koolaid struct / value в Swift. И вот у меня возникла интересная проблема, которую я не знаю, как решить. У меня есть структура, которая является контейнером, например.

struct Foo {
    var bars:[Bar]
}

Внося в него правки, я создаю копии, чтобы сохранить стек отмены. Все идет нормально. Как показали хорошие уроки. Однако есть некоторые производные атрибуты, которые я использую с этим парнем:

struct Foo {
    var bars:[Bar]

    var derivedValue:Int {
        ...
    }
}

В недавнем профилировании я заметил: а) что вычисления для вычисления производного значения являются своего рода дорогостоящими / избыточными б) не всегда необходимы для вычислений в различных случаях использования.

В моем классическом способе ООП я бы сделал это мемоизирующей / ленивой переменной. По сути, пусть он будет равен нулю, пока он не будет вызван, вычислить его один раз, сохранить его и вернуть указанный результат при будущих вызовах. Поскольку я следую шаблону «копировать для редактирования», инвариант не будет нарушен.

Но я не могу понять, как применить этот шаблон, если он структурный. Я могу сделать это:

struct Foo {
    var bars:[Bar]
    lazy var derivedValue:Int = self.computeDerivation()
}

который работает, пока структура не ссылается на это значение, например

struct Foo {
    var bars:[Bar]
    lazy var derivedValue:Int = self.computeDerivation()

    fun anotherDerivedComputation() {
        return self.derivedValue / 2
    }
}

На этом этапе компилятор жалуется, потому что anotherDerivedComputation вызывает изменение в приемнике и, следовательно, должен быть помечен как mutating. Кажется неправильным помечать аксессуар как изменяющийся. Но что касается ухмылки, я пробую, но это создает новую массу проблем. Теперь везде, где у меня есть выражение вроде

XCTAssertEqaul(foo.anotherDerivedComputation(), 20)

компилятор жалуется, потому что параметр неявно является неизменяемым значением let, а не var.

Есть ли шаблон, который мне не хватает для структуры с отложенным / ленивым / кешированным членом?


person Travis Griggs    schedule 07.08.2018    source источник


Ответы (3)


Мемоизации не происходит внутри структуры. Способ запоминания - хранить словарь в отдельном пространстве. Ключ - это то, что используется для получения значения, а значение - это значение, рассчитанное один раз. Вы можете сделать его статическим по отношению к типу структуры, просто как способ размещения его имен.

struct S {
    static var memo = [Int:Int]()
    var i : Int
    var square : Int {
        if let result = S.memo[i] {return result}
        print("calculating")
        let newresult = i*i // pretend that's expensive
        S.memo[i] = newresult
        return newresult
    }
}

var s = S(i:2)
s.square // calculating
s = S(i:2)
s.square // [nothing]
s = S(i:3)
s.square // calculating
person matt    schedule 07.08.2018
comment
Хотя кеш словарного стиля часто является распространенной реализацией мемоизации, я не думаю, что этот термин явно ограничен указанными реализациями. Википедия утверждает, что мемоизированная функция запоминает результаты, соответствующие некоторому набору определенных входных данных. Но, возможно, термин вводит в заблуждение, не лучше ли было бы кеширование в названии? - person Travis Griggs; 07.08.2018
comment
Что ж, позвольте мне сказать по-другому. Поможет ли запоминание в стиле, который я предлагаю, решить проблему? - person matt; 07.08.2018
comment
Добавлен пример того, что я предлагаю. - person matt; 07.08.2018
comment
Да, это сработает. Нижняя строка, похоже, заключается в том, что вам нужно где-то поместить изменяемое значение (будь то в поле свойств кеширования или в таблице в стороне). - person Travis Griggs; 07.08.2018
comment
Итак, кто здесь получает ответ? Ваши ответы и ответы @OleBegemann направили меня в правильном направлении. И у вас обоих безумное количество очков. :) - person Travis Griggs; 08.08.2018
comment
Вы можете принять свой ответ! Подождите 48 часов, затем примите. Он закрывает вопрос, и он закрывает его правильно: ваш ответ - правильный ответ для вас, и это было не то, что мы сказали. - person matt; 08.08.2018

Я знаю, что единственный способ выполнить эту работу - обернуть ленивого члена в класс. Таким образом, структура, содержащая ссылку на объект, может оставаться неизменной, в то время как сам объект может быть изменен.

Несколько лет назад я написал в блоге сообщение на эту тему: Ленивые свойства в структурах. В нем более подробно рассматриваются особенности и предлагаются два разных подхода к проектированию класса-оболочки, в зависимости от того, нужна ли ленивому члену информация об экземпляре из структуры для вычисления кэшированного значения или нет.

person Ole Begemann    schedule 07.08.2018

Я обобщил проблему до более простой: структура x, y Point, которая хочет лениво вычислять / кэшировать значение для r (adius). Я использовал обертку ref вокруг замыкания блока и придумал следующее. Я называю это блоком «Однажды».

import Foundation

class Once<Input,Output> {
    let block:(Input)->Output
    private var cache:Output? = nil

    init(_ block:@escaping (Input)->Output) {
        self.block = block
    }

    func once(_ input:Input) -> Output {
        if self.cache == nil {
            self.cache = self.block(input)
        }
        return self.cache!
    }
}

struct Point {
    let x:Float
    let y:Float
    private let rOnce:Once<Point,Float> = Once {myself in myself.computeRadius()}

    init(x:Float, y:Float) {
        self.x = x
        self.y = y
    }

    var r:Float {
        return self.rOnce.once(self)
    }

    func computeRadius() -> Float {
        return sqrtf((self.x * self.x) + (self.y * self.y))
    }
}

let p = Point(x: 30, y: 40)

print("p.r \(p.r)")

Я сделал выбор, чтобы OnceBlock принимал входные данные, потому что в противном случае инициализация его как функции, имеющей ссылку на self, является проблемой, потому что self еще не существует при инициализации, поэтому было проще просто отложить эту связь с кешем / позвонить на сайт (var r:Float)

person Travis Griggs    schedule 07.08.2018
comment
Весело и полезно одновременно! - person matt; 07.08.2018