Memoization of Swift properties

The problem with using a PropertyWrapper isn't that it's a struct. It's that it's initialized by the class or struct that uses it. In order to have a reference, the class or struct would have to pass self. However, it has to do so before it's fully initialized itself. To work, PropertyWrapper would have to be able to wait until it's caller was fully-initialized before being initialized.

This feels like a problem PropertyWrappers should be able to solve at first, but the more I've looked at it, the more I feel that this is really a different sort of issue. PropertyWrapper are really great for self-contained logic around a property. However, they really aren't designed to manage their caller. A PropertyWrapper solution for this feels like opening the door to reference cycles, etc.

I really tried to think of every way I could to make this as a PropertyWrapper, but it just seems like it's not the right tool for the job. I came to the conclusion that a better solution was to rely on property observers. This still doesn't quite work on reference type properties. However, I wonder if we could use a similar approach to Combine... so a dependent reference-type would have to implement ObservableObject and publish the needed properties to work with memoization. (This sort of revives the use of PropertyWrappers but on the other side of the equation).

It's just an information for this topic.
I've created a flux library(Verge) and this supports Computed properties with Memoization inside a state structure.

This has been built using a class object that contains the latest state and the previous state.
Through the declaration of Computed property, it returns a computed value that value could be that already computed if its dependencies not changed.

the syntax is like the following:

struct MyState: Verge.ExtendedStateType {

  var name: String = ...
  var items: [Int] = []

  struct Extended: Verge.ExtendedType {

    static let instance = Extended()

    let filteredArray = Field.Computed<[Int]> {
      $0.items.filter { $0 > 300 }
    }
    .dropsInput {
      $0.noChanges(\.items)
    }
  }
}
let store: Verge.Store<MyState, Never> = ...

let state: Verge.Changes<MyState> = store.state

let result: [Int] = state.computed.filteredArray

Verge.Changes contains the latest state and the previous state.

If you're curious, please try to read this document:
https://vergegroup.github.io/Verge/docs/VergeStore/extended-computed-property

2 Likes

Very cool... I'll have to check this out!

Thanks for reading.
And so you know, that approach Verge uses several issues(weakness points).
the syntax of the definition that is not clear and smart since using proxy types to get a syntax like property accessing by dynamicMemberLookup.
That is to get syntax as possible as natural.

In my view, it would be nice if Swift supports memorized computed property in language level.

1 Like

Totally agree! I'll need to dig a bit deeper when I have the time, but I think there could be a lot of good building blocks in what you're doing to bring memoization to the language level. Please feel free to make some suggestions!

I think it highlights the basic issue: memoization is very doable with the language today, just not simple or pretty. I think most of the tools to get this done are already in front of us. All that's really needed are a few behind-the-scenes shortcuts and syntactic sugar.

Is your solution able to deal with changes to reference types? That seems to be the biggest challenge.

You mean can we support that if the container type is reference-type?
Now my solution can't do that. If we do that, it needs something additional strategy. For example, adding a protocol that indicates itself can clone.
Because you know the necessity of re-computing comes from the previous and current value.
Using reference-type cause the issues that caching-logic.
Caching-logic will miss changes from modifying with reference.

And currently, my solution already has that issue, if the value-type contains something reference-type and used in memoized computed properties, it's going to be broke.
So we can say, We need a feature in Swift language that detects Memoized computed property works correctly in run-time.

So your proposal syntax will work well, I think.
But if we might be able to get this feature without additional syntax that indicates dependencies.
Because Swift compiler can detect dependencies of outside in scope in compile time. (like using self inside escaping closure) if something issues found, Swift can raise errors or warnings.
What do you think?

Sorry for my verbose replying.

1 Like

Yup... thinking the solution for reference types would have to be somewhat like Combine's @ObservableObject.