Friday, June 27, 2014

Doubly optional values

In Swift, it's perfectly legal to have a value of type T??, and can be very useful.

For example, if you take a sequence like a: Int?[] = [0, nil, 1, nil], and attempt to iterate over it, the generator has to give you values of type Int??. And, not only that, it has to be able to distinguish between an Int?? value that's nil (meaning iteration is done) and an Int?? value that has a real Int? value that happens to be nil (meaning it's just giving you a[1]).

This is a lot easier to think about in a language that doesn't try to hide the nilness. For example, in Haskell, if you have a Maybe Maybe Int, it could have any of these values:

  • Nothing
  • Just Nothing
  • Just Just 0
But in Swift, the equivalent Int?? values are:
  • nil
  • nil
  • 0
Even when displayed with the types, there's no help:
  • Int?? = nil
  • Int?? = nil
  • Int?? = 0
So, how can you distinguish them? Just comparing to nil won't help, as both compare equal to nil. But let binding works—bind an Int?? to an Int?, and the binding is false for the first case, but true for the second:
    if let a = foo { println("\(a)") } else { println("really nothing") }
This will print:
  • really nothing
  • nil
  • 0
So, everything's good. For example, these code fragments do the same thing, as they should:
    let a: Int?[] = [0, nil, 1, nil]

    for i in a { println("\(i)") }

    var g = a.generate()
    while let i = g.next() { println("\(i)") }
Except that once generic functions get involved, everything breaks. Try either of the following:
    func printem(s: S) {
        var g = s.generate()
        while let i = g.next() { println("\(i)") }
    }
    printem(a)

    func printem(s: S) {
        for i in s { println("\(i)") }
    }
    printem(a)
If you try to call either version with a sequence whose element type is optional, you get a compiler crash. In the first case, I think it's crashing while trying to infer types to compile the let binding, but putting an explicit i: T there doesn't change anything, so… who knows.

Oddly, if the type of a isn't an Array of optionals, but a custom struct, instead of a crash, it seems to successfully compile the wrong code, trying to bind the T?? directly to a T, and therefore treating both nil values and the end of the sequence the same.

1 comment:

  1. When I've needed to dig into Swift Optionals and do things based on their exact structure, I've found it easiest to treat them as their true nature— enums.

    Here's a quick function I cooked up that differentiates between your three types just switch/case-ing on the Int??

    func printType(_ ooi:Int??) {
        switch ooi {
            case .some(.some(let i)):
                print("\(i)")
            case .some(.none):
                print("nil")
            case .none:
                print("really nothing")
        }
    }

    printType(nil) // prints "really nothing"
    printType(Int?.none) // prints "nil"
    printType(0) // prints "0"

    ReplyDelete