Increase the power of optionals

So I was thinking about how troublesome it can be to deal with optional variables sometimes. So here's a situation that raises the question on if we can make optionals more expressive and powerful with a new type of optional binding.

let someView = UIView(frame : .zero)
var someOptionalLabel : UILabel? 
/// ...
/// many lines later
if someConditionThatRequiresMeToInitalizeLabel {
    /// Need to initialize the label which we may forget to do or delete by accident
    someOptionalLabel = UILabel(frame: .zero)
    if let someLabel = someOptionalLabel {
       /// Setup the label
       someLabel.text = "Hello World"
       /// ....
       someView.addSubview(someLabel)
    } else {
      /// Handle nil `someOptionalLabel`, usually crashing the app or logging to the backend about someone forgetting to initialize 
      fatalError()
    }
}

I am aware that the optional in the beginning was not needed. The above is a sample situation for the sake of the point.Nonetheless, It would be nice to have a feature like below. Notice the keyword on .

let someView = UIView(frame : .zero)
var someOptionalLabel : UILabel? 
/// ...
/// many lines later
if someConditionThatRequiresMeToInitalizeLabel {
    /// `on` requires non-nil initializer. 
    on someOptionalLabel = UILabel(frame: .zero) {
        /// Since we initialized the Optional, we can use it as if it was good to go 
        /// No need to do someOptionalLabel?.text = "Hello World"
        someOptionalLabel.text = "Hello World"
        /// ...
        someView.addSubview(someOptionalLabel)
        
    }
}

If more than 1 on statements is used for a variable in the same scope, the compiler can generate errors. There is a fallacy I see which can cause misuse of the feature and that is multiple on statements for the same variable within multiple conditional scopes, where people keep reinitializing the same variable that has been initialized before. But Im open to hear what you all think.

why can’t you do this?

if someConditionThatRequiresMeToInit{i}alizeLabel 
{
    if let someLabel = UILabel(frame: .zero) 
    {
        someLabel.text = "Hello World"
        someView.addSubview(someLabel)

        someOptionalLabel = someLabel
    } 
    else 
    {
        fatalError()
    }
}

it’s a lot more clearer anyway, since your original version doesn’t make much sense unless you already know UILabel is a class.

1 Like
"Initializer for conditional binding must have Optional type, not 'UILabel'" 

If I was using a failable initializer for UILabel, it would work. This is about binding optional variables of types that have non-failable initializers.

I may just be reaching for nothing on this one but just an idea.

if someConditionThatRequiresMeToInitializeLabel 
{
    let someLabel:UILabel = .init(frame: .zero) 
    
    someLabel.text = "Hello World"
    someView.addSubview(someLabel)

    someOptionalLabel = someLabel
}

or if youre worried about forgetting to assign someOptionalLabel

if someConditionThatRequiresMeToInitializeLabel 
{
    let someLabel:UILabel = .init(frame: .zero) 
    defer 
    {
        someOptionalLabel = someLabel
    }
    
    someLabel.text = "Hello World"
    someView.addSubview(someLabel)
}

Yea using a class was not a good example.

This is the go to approach. What I was trying to convey was an idea. Not a good one apparently.

Unless I'm missing something in the wider context of this ViewController, it seems like someOptionalLabel need not be optional at all. Instead, you can make it someLazyLabel:

let someView = UIView(frame : .zero)
lazy var someLazyLabel: UILabel = {
    let label = UILabel(frame: .zero)
    /// Setup the label - if you like
    /// ....
    someView.addSubview(label)
    return label
}()

/// ...
/// many lines later
if someConditionThatRequiresMeToInitalizeLabel {
    someLazyLabel.text = "Hello World"
}

That way, you no longer need to spoil your day with if let or fatalError.

1 Like

To solve this kind of problem, I always recommend the with(_:do:) function:

func with<T>(_ value: T, do body: (inout T) throws -> Void) rethrows -> T {
  var temp = value
  try body(&temp)
  return temp
}

Use it like this:

if someConditionThatRequiresMeToInitalizeLabel {
  someOptionalLabel = with(UILabel(frame: .zero)) {
    $0.text = "Hello World"
    someView.addSubview($0)
  }
}
5 Likes

I would also like to add that, if your else does nothing more than fatalError then you should consider unwrapping with !

3 Likes

fatalError lets you output an error message though!

2 Likes

Any reason not to use a function?

let someView = UIView(frame : .zero)
var someOptionalLabel : UILabel? 

if someConditionThatRequiresMeToInitalizeLabel {

    someOptionalLabel = {

        let newLabel = UILabel(frame: .zero)
        newLabel.text = "Hello World"
        someView.addSubview(newLabel)
        return newLabel
    }()
}

or, if you need to use it,

let localLabel = someOptionalLabel ?? {

    let newLabel = UILabel(frame: .zero)
    newLabel.text = "Hello World"
    someView.addSubview(newLabel)
    someOptionalLabel = newLabel
    return newLabel
}()

A more generalized addition I would love to see is supporting something like Kotlin's Scope functions, (similar to @brentdax's suggestion) e.g. also which improves this use case.

let someView = UIView(frame : .zero)
var someOptionalLabel : UILabel? 

if someConditionThatRequiresMeToInitalizeLabel {

    someOptionalLabel = UILabel(frame: .zero).also {
        //The result of the previous function is passed in as a parameter and then returned
        $0.text = "Hello World"
        someView.addSubview($0)    
    }
}

Indeed, and if that's preferable then you can play with SE-0217

Terms of Service

Privacy Policy

Cookie Policy