DEV Community

Florian Schliep
Florian Schliep

Posted on • Updated on

Experiment: Lazy Objects in Swift

NEW: This blog post is also available on my personal website alongside even more content!

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 let doesn’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.

Using our 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?

Top comments (3)

Collapse
 
lobsterpants66 profile image
Chris Shepherd

Just a personal observation but I hate code examples using foo.bar. I'm sure loads of people will tell me I'm an idiot, but I just find it makes code unreadable.

In 20 years of coding I have never barred a foo.... But I guess that's just me.

Collapse
 
floschliep profile image
Florian Schliep

Thanks for the feedback, Chris! What would you have used?

I also considered using MyObject and SomeObject, as I needed two types, but Foo and Bar almost feel like a convention now. I'm not opposed to more readable names though!

Collapse
 
lobsterpants66 profile image
Chris Shepherd

Yeah Foo.Bar is a convention and lots of people seem OK with it.
I just find practical examples easier to read, something like Person.Age tends to be easier for my brain to absorb.

I'm always looking at examples thinking "when/why would I use this?", your post is perhaps a bit esoteric for a practical example but interesting none the less.