Bool vs. Optional<Void>


(Brent Royal-Gordon) #1

This is less a proposal and more a vague notion, but here goes.

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`.

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?

···

--
Brent Royal-Gordon
Architechies


(Krzysztof Siejkowski) #2

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?
I’m against unifying those concepts, for two main reasons:

1) These are two separate constructs. When Bool is turned into Optional<Void>, it’s logical to be able to map it to Optional<T>, to flatMap it, to forEach it etc. I can’t see a situation where mapping Bool to, let’s say, Optional<Array<String>> is a readable statement.

2) Bool is a very basic type, one of the first to use when learning programming. Making it an alias for a widely more complicated construct will introduce special cases that will be misleading for those learning to code.

I believe the situation where Optional<Void> is used to indicate the existence or absence of particular condition could be better resolved. Two alternatives that come to mind:

a) „isDefined” method on Optional, converting any Optional to Bool

b) just writing optionalVoid.map { return true } ?? false at the end of your pipe to transform otherwise ambiguous Void? to Bool clearly expressing your thoughts.

Krzysztof

···

--
Brent Royal-Gordon
Architechies

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Pyry Jahkola) #3

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

···

On 17 Dec 2015, at 12:25, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> wrote:


(Andrew Bennett) #4

Interesting proposal, I think there's value in exploring this, it certainly
seems logical. I'd like to see the impact on an existing codebase and if
there's any savings/conveniences/issues.

My main concerns, mostly that Swift is not yet ready for it:
* How does this compare semantically when used in an if/guard statement?
* If higher kinded types are introduced should "Optional<Int>" be of a
kind with "Bool" ?
* Optional casting rules need some work still, in my opinion.
* How does this relate to "ImplicitlyUnwrappedOptional<Void>" ? they seem
sometimes interchangeable with "Optional<Void>".
* As you mentioned, but in more detail:
     + Making it conform to "BooleanType", "BooleanLiteralConvertible" is
not yet possible:

*error: same-type requirement makes generic parameter 'Wrapped' non-generic*

extension Optional where Wrapped == Void { /*...*/ }

     + Can we, or even should we specialise debugDescription for
CustomDebugStringConvertible ?
* Assuming the generic is equivalent to this:

enum Bool: BooleanType, BooleanLiteralConvertible {

    case Some()

    case None

}

     + Will that be optimised to the same extent as Bool?
     + Will it be packed as efficiently as Bool (Int1)?
     + Is "let myBoolean = .None" confusing? should it be allowed?

···

On Thu, Dec 17, 2015 at 9:25 PM, Brent Royal-Gordon via swift-evolution < swift-evolution@swift.org> wrote:

This is less a proposal and more a vague notion, but here goes.

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`.

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?

--
Brent Royal-Gordon
Architechies

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Félix Cloutier) #5

I'm also against unification. I think that Krzysztof makes a good point showing what you can do on an Optional but shouldn't do on booleans.

In my opinion, going forward, code that uses Void? to signal failure should move to either Bool or to exceptions anyway. If this encourages the idiom to stay, it might not be in the best interest of the language.

Félix

···

Le 17 déc. 2015 à 06:36:50, Krzysztof Siejkowski via swift-evolution <swift-evolution@swift.org> a écrit :

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?

I’m against unifying those concepts, for two main reasons:

1) These are two separate constructs. When Bool is turned into Optional<Void>, it’s logical to be able to map it to Optional<T>, to flatMap it, to forEach it etc. I can’t see a situation where mapping Bool to, let’s say, Optional<Array<String>> is a readable statement.

2) Bool is a very basic type, one of the first to use when learning programming. Making it an alias for a widely more complicated construct will introduce special cases that will be misleading for those learning to code.

I believe the situation where Optional<Void> is used to indicate the existence or absence of particular condition could be better resolved. Two alternatives that come to mind:

a) „isDefined” method on Optional, converting any Optional to Bool

b) just writing optionalVoid.map { return true } ?? false at the end of your pipe to transform otherwise ambiguous Void? to Bool clearly expressing your thoughts.

Krzysztof

--
Brent Royal-Gordon
Architechies

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(Slava Pestov) #6

This is an artifact of the current implementation more than anything. IIRC there are missed optimization opportunities with an enum implementation, but the goal is to move to that.

Slava

···

On Dec 17, 2015, at 11:01 AM, Pyry Jahkola via swift-evolution <swift-evolution@swift.org> wrote:
(Possibly related to this is that Bool is a struct and not an enum in Swift.)


(Jordan Rose) #7

This idea has actually come up before, but ultimately we came to the same conclusion as most people here: just because they're isomorphic and fairly similar doesn't mean they're really the same type. (For example, we have some auto-corrections if you try to use an Optional as a Bool. But if your Optional is coincidentally Optional<Void>, suddenly your code is valid! This can happen with optional chaining.)

···

On Dec 17, 2015, at 11:01 , Pyry Jahkola via swift-evolution <swift-evolution@swift.org> wrote:

(Possibly related to this is that Bool is a struct and not an enum in Swift.)

We've tossed around the idea of going to an enum now that we actually have efficient enum-handling instructions in SIL. I think there's still a bit of work to be done there, though.

Jordan