Declaring local variables as lazy

Here's a real world use case (that could end up in the Standard Library).

It overrides the default O(n) implementation of distance(from:to:) with an O(1) implementation for a zip that takes default values:

func distance (from start: Index, to end: Index) -> Int {
  if start == end {
    return 0
  }

  let distance1 = collection1.distance(from: start.index1, to: end.index1)
  let distance2 = collection2.distance(from: start.index2, to: end.index2)

  if start < end {
    return Swift.min(
      defaultElement1 == nil ? distance1 : Swift.max(distance1, distance2),
      defaultElement2 == nil ? distance2 : Swift.max(distance1, distance2)
    )
  } else {
    return Swift.max(
      defaultElement1 == nil ? distance1 : Swift.min(distance1, distance2),
      defaultElement2 == nil ? distance2 : Swift.min(distance1, distance2)
    )
  }
}

The ternary operator already helps us out here; if no defaultElements exist, the calls to Swift.max(_:_:) and Swift.min(_:_:) will not be made. However, if both Collections have defaultElements, then either will be computed twice.

With lazy vars this could be rewritten as:

func distance (from start: Index, to end: Index) -> Int {
  if start == end {
    return 0
  }

  let distance1 = collection1.distance(from: start.index1, to: end.index1)
  let distance2 = collection2.distance(from: start.index2, to: end.index2)

  if start < end {
    lazy var max = Swift.max(distance1, distance2)

    return Swift.min(
      defaultElement1 == nil ? distance1 : max,
      defaultElement2 == nil ? distance2 : max
    )
  } else {
    lazy var min = Swift.min(distance1, distance2)

    return Swift.max(
      defaultElement1 == nil ? distance1 : min,
      defaultElement2 == nil ? distance2 : min
    )
  }
}

It might very well be that the cost of using lazy var here outweighs the potential duplicated calls to min(_:_:) and max(_:_:), since these are not the most computationally heavy calculations. But in cases where the calculative cost matters, local lazy variables would be incredibly useful.

Re: workarounds. Closures and function wrappers will only work if the variable it's wrapping is only used once. Breaking the variable out into the namespace above it, pollutes it and is considered bad practice. A lazy struct wrapper is the most elegant hack, but has extra cognitive overhead.

This should be in the language.

8 Likes