Home > Software design >  What is the difference between "lazy var funcName: () -> Void" and regular function dec
What is the difference between "lazy var funcName: () -> Void" and regular function dec

Time:08-24

I have recently encountered such code block when I was working on a different codebase.

I was wondering is there any significant difference (in terms of memory impact etc.) between these two declarations other than syntactic ease? I regularly use lazy stored properties but I couldn't visualize how a function can be "lazy". Can you guys also enlighten me how functions are processed (or share an article explaining this topic) by the swift compiler as well?

enter image description here

Thanks in advance, wish you bug free codes.

CodePudding user response:

A few differences I can think of off the top of my head:

  • With functions, you can use parameter labels to make the call site more readable, but since you can't add parameter labels to function types, you can't do that with the lazy var declaration.

      // doesn't work
      lazy var say: (message: String, to person: String) -> Void = {
          ...
      }
    
      // works
      func say(message: String, to person: String) {
          ...
      }
    

    You can only invoke a function type with no parameter labels at all :( say("Hello", "Sweeper") instead of say(message: "Hello", to: "Sweeper").

  • lazy vars are variables, so someone could just change them:

      helloFunc = { /* something else */ }
    

    you could prevent this by adding a private setter, but that still allows setting it from within the same class. With a function, its implementation can never be changed at runtime.

  • lazy vars cannot be generic. Functions can.

      // doesn't work
      lazy var someGenericThing<T>: (T) -> Void = {
          ...
      }
    
      // works
      func someGenericThing<T>(x: T) {
          ...
      }
    

    You might be able to work around this by making the enclosing type generic, but that changes the semantics of the entire type. With functions, you don't need to do that at all.

CodePudding user response:

If you're implementing a language, the magic you need to implement 'lazy' as a feature is to make the value provided silently be wrapped in a function (of no arguments) that is automatically evaluated the first time the property is read.

So for example if you have

var x = SomeObject() // eagerly construct an instance of SomeObject

and

lazy var x = SomeObject() // construct an instance if/when someone reads x

behind the scenes you have, for the lazy var x, either x has not yet been read, or it has been and therefore has a value. So this is like

  enum Lazy<T> {
      case notReadYet(() -> T) // either you have a function that makes the initial T
      case read(T) // or you have a value for T
  }

 var thing: Lazy<SomeObject> = .notReadYet { return SomeObject() }

and with a suitable property wrapper or other language magic, wrap the calls to the getter for thing with a switch on the case, which checks if you are in the notReadYet case, and if so automatically invoke the function to produce the T and then set the property to .read(t) for the particular value of t.

If you substitute in your type: () -> Void from your example you have something like:

 var thing: Lazy<() -> Void> = .notReadYet({ return { print("Hello") } })

This is all rather odd because it's a void value and not much point making it lazy, e.g. it's not expensive to compute; it doesn't help break a two-phase initialisation loop, so why do it?

  • Related