Disclaimer: This post is purely experimental based on an idea I had recently, I’m personally not using this in production right now.
Laziness in Swift can be a very powerful tool, but it hasn’t reached its full potential yet. Recently, I came across a situation where I wanted to write something likes this:
In other words: I wanted to pass the closure for creating a lazy property to the initializer of my class and make it a constant property. This doesn’t work because of two reasons:
- Properties are being evaluated upon instantiation of the object, except for lazy variables.
lazy letdoesn’t exist in Swift.
- Lazy properties must declare their initializer, meaning you can’t pass a closure as the initializer to it.
With those constraints, our only option is to use existing features to make this work. Here’s what I expect from my implementation:
- Pass a closure, which won’t be evaluated until it’s necessary, to something.
- The closure should only be evaluated once, otherwise this would defeat the purpose of the whole concept as we could just use a normal closure.
- This should be universally usable with anything.
- A readable, easy-to-understand syntax.
No 3 will be easy to satisfy by using Generics. Due to No 1 & 2 it will be necessary to use a
class, as evaluating and storing the result of our closure implicitly mutates the object.
LazyObject wrapper now looks like this:
Only on the last line the closure will actually be evaluated and our
Bar object created. Calling
bar.object again would just return the cached object and not evaluate the closure again.
So far so good. What about structs though? Right now, our wrapper doesn’t allow mutating the underlying
object. Let’s create a mutable subclass,
MutableLazyObject, that provides a scope allowing mutation:
By moving the
use(_:) method to a subclass, we can explicitly decide whether we want to allow mutation or not.
Unfortunately though, using the
LazyObject class doesn’t look nice, violating expectation No 4. Let’s write a short helper function:
This function allows us to use the shorthand syntax
lazy(Bar()). Much better!
The full code, including tests, is available on GitHub. I’d love to get some feedback on this! Is this a good idea? Why or why not? Should Swift allow this kind of functionality on the language level?