[swift-evolution] Bool vs. Optional<Void>

Pyry Jahkola pyry.jahkola at iki.fi
Thu Dec 17 13:01:58 CST 2015


> On 17 Dec 2015, at 12:25, Brent Royal-Gordon via swift-evolution <swift-evolution at swift.org> wrote:
> 
> Occasionally, Optional<Void> comes up in Swift. This particularly happens with optional chaining (and its socially awkward twin `Optional.flatMap(_:)`), but I occasionally see it come up in other places, particularly where you’re using a Result-ish type. Wherever it does come up, it seems to have the same semantic meaning as Bool, with `nil` meaning `false` and `()` meaning `true`.

It's true that Optional<Void> corresponds to Bool, but those aren't the only isomorphic types to each other. There's also Bit, and I could list a lot of other more domain-specific enums that just happen to have two cases with no associated data.

I think it would be very confusing to people when Optional<Void>.Some() were suddenly a common thing. (Possibly related to this is that Bool is a struct and not an enum in Swift.)

> Does it make sense to somehow unify them? Perhaps turn Bool into a typealias for Optional<Void> and move all of its conformances into (not yet supported) conditional conformances on Optional?

The one thing I'd suggest instead is if Void were made Equatable. In that case, you'd convert from Optional<Void> to Bool by simply comparing against ():

let optional: ()? = ...
let bool = optional == ()

And before someone says it's impossible because tuples aren't nominal types in Swift (yet), this all could be emulated (for now) by overloading == and !=:

@warn_unused_result
func ==(_: (), _: ()) -> Bool { return true }

@warn_unused_result
func !=(_: (), _: ()) -> Bool { return false }

@warn_unused_result
func ==(a: ()?, b: ()?) -> Bool {
    switch (a, b) {
    case (()?, ()?), (nil, nil): return true
    default: return false
    }
}

@warn_unused_result
func !=(a: ()?, b: ()?) -> Bool {
    return !(a == b)
}

Another question though is whether not defining () == () also servers as a sanity check against making something stupid like comparing the result of a side-effectful function against nil by mistake. But it turns out the compiler is actually pretty clever against unintended Void or Void?:

class Missile {
    // Maybe it once returned `()?` or something.
    //func launch() -> ()? { return nil }
    func launch() {}
}

let result = Missile().launch()
//  ^
// warning: constant 'result' inferred to have type '()', which may be unexpected
// note: add an explicit type annotation to silence this warning

let explicit: ()? = result // no warning

let bool = result != nil
//         ~~~~~~ ^
// error: value of type '()' can never be nil, comparison isn't allowed

let optional = Optional(result)
//  ^
// warning: constant 'optional' inferred to have type 'Optional<()>', which may be unexpected

if optional == () { // No warning.
    print("Missile launched, I'm afraid!")
}

— Pyry Jahkola

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20151217/44ffd76a/attachment.html>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 842 bytes
Desc: Message signed with OpenPGP using GPGMail
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20151217/44ffd76a/attachment.sig>


More information about the swift-evolution mailing list