[Idea] Repurpose Void


(Anton Zhilin) #1

SE-0066 disallows Void to be used on left side of function types.

Some people, including me, argue that Void should be removed altogether,
because:
1) () is more consistent with curried functions: (Int) -> () -> Double
2) () follows functional programming traditions

Now, why don't we repurpose Void to follow functional programming
traditions as well?
Its definition will look like:
enum Void { }

Basically, Void is a type which cannot have values.
With it, we can eliminate at least two Swift special cases:

1. Noreturn functions

func exit(code: Int = 0) -> Void

From this signature, it's obvious that `exit` cannot return normally

2. Rethrows

func call<T, U>(block: () throws T -> U) throws T -> U

Non-throwing functions are functions throwing Void.
So if T=Void, we get the non-throwing version of `call`.

- Anton


(Brent Royal-Gordon) #2

Some people, including me, argue that Void should be removed altogether, because:
1) () is more consistent with curried functions: (Int) -> () -> Double
2) () follows functional programming traditions

Now, why don't we repurpose Void to follow functional programming traditions as well?
Its definition will look like:
enum Void { }

Basically, Void is a type which cannot have values.

I don't think Void is a good name for this, because it means something wildly different from the C-style `void`. It would be a confusing name choice for no real reason.

With it, we can eliminate at least two Swift special cases:

1. Noreturn functions

func exit(code: Int = 0) -> Void

From this signature, it's obvious that `exit` cannot return normally

2. Rethrows

func call<T, U>(block: () throws T -> U) throws T -> U

Non-throwing functions are functions throwing Void.
So if T=Void, we get the non-throwing version of `call`.

As for these, I think we would actually be better off having a bottom type for these use cases. A bottom type is a subtype of all types—sort of the opposite of `Any` (which is at the top of the type graph). Since it's impossible for any value to belong to all types simultaneously, it's impossible to construct a value of the bottom type, so it has the same role of meaning "this never returns/happens". But it has an important advantage over an empty enum type: you can treat it as any type you'd like.

For instance, suppose we spell the bottom type `_`. Then we can write, for instance, this function, which indicates we haven't finished writing something:

  func unimplemented(_ description: String, file: String = #file, line: String = #line) -> _ {
    fatalError("\(description) unimplemented", file: file, line: line)
  }

And use it like so:

  func calculateThing() -> Thing {
    if let cachedThing = cachedThing {
      return cachedThing
    }
    
    cachedThing = Thing(foo: calculateFoo(), bar: unimplemented("Calculation of bar"))
    return cachedThing!
  }

Because `unimplemented()` returns the bottom type, we can use it anywhere in any expression expecting any type, and it will compile just fine, implicitly converting to any type the context demands. Of course, since there are no values of the bottom type, `unimplemented()` cannot actually return a value, and so the call to `Thing.init(foo:bar:)` can never actually occur. But from the type checker's perspective, it all works out fine.

···

--
Brent Royal-Gordon
Architechies


(Dave Abrahams) #3

Repurposing the name "Void" in this way would move Swift definitively
away from being a C-family language. If we were to do that, we should
do it very consciously and with full awareness of the larger
implications. I.e. this isn't just about one name.

···

on Sat Apr 23 2016, Антон Жилин <swift-evolution@swift.org> wrote:

SE-0066 disallows Void to be used on left side of function types.

Some people, including me, argue that Void should be removed altogether,
because:
1) () is more consistent with curried functions: (Int) -> () -> Double
2) () follows functional programming traditions

Now, why don't we repurpose Void to follow functional programming traditions as
well?
Its definition will look like:
enum Void { }

Basically, Void is a type which cannot have values.
With it, we can eliminate at least two Swift special cases:

1. Noreturn functions

func exit(code: Int = 0) -> Void

From this signature, it's obvious that `exit` cannot return normally

2. Rethrows

func call<T, U>(block: () throws T -> U) throws T -> U

Non-throwing functions are functions throwing Void.
So if T=Void, we get the non-throwing version of `call`.

--
Dave


(Dennis Lysenko) #4

Just wanted to quickly throw out that Kotlin's Unit class, their
"equivalent" of void, semantically follows the definition of "type with
only one value" laid out in the enum Void example above.

Dennis

···

On Sun, Apr 24, 2016, 9:43 PM Brent Royal-Gordon via swift-evolution < swift-evolution@swift.org> wrote:

> Some people, including me, argue that Void should be removed altogether,
because:
> 1) () is more consistent with curried functions: (Int) -> () -> Double
> 2) () follows functional programming traditions
>
> Now, why don't we repurpose Void to follow functional programming
traditions as well?
> Its definition will look like:
> enum Void { }
>
> Basically, Void is a type which cannot have values.

I don't think Void is a good name for this, because it means something
wildly different from the C-style `void`. It would be a confusing name
choice for no real reason.

> With it, we can eliminate at least two Swift special cases:
>
> 1. Noreturn functions
>
> func exit(code: Int = 0) -> Void
>
> From this signature, it's obvious that `exit` cannot return normally
>
> 2. Rethrows
>
> func call<T, U>(block: () throws T -> U) throws T -> U
>
> Non-throwing functions are functions throwing Void.
> So if T=Void, we get the non-throwing version of `call`.

As for these, I think we would actually be better off having a bottom type
for these use cases. A bottom type is a subtype of all types—sort of the
opposite of `Any` (which is at the top of the type graph). Since it's
impossible for any value to belong to all types simultaneously, it's
impossible to construct a value of the bottom type, so it has the same role
of meaning "this never returns/happens". But it has an important advantage
over an empty enum type: you can treat it as any type you'd like.

For instance, suppose we spell the bottom type `_`. Then we can write, for
instance, this function, which indicates we haven't finished writing
something:

        func unimplemented(_ description: String, file: String = #file,
line: String = #line) -> _ {
                fatalError("\(description) unimplemented", file: file,
line: line)
        }

And use it like so:

        func calculateThing() -> Thing {
                if let cachedThing = cachedThing {
                        return cachedThing
                }

                cachedThing = Thing(foo: calculateFoo(), bar:
unimplemented("Calculation of bar"))
                return cachedThing!
        }

Because `unimplemented()` returns the bottom type, we can use it anywhere
in any expression expecting any type, and it will compile just fine,
implicitly converting to any type the context demands. Of course, since
there are no values of the bottom type, `unimplemented()` cannot actually
return a value, and so the call to `Thing.init(foo:bar:)` can never
actually occur. But from the type checker's perspective, it all works out
fine.

--
Brent Royal-Gordon
Architechies

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


(Brent Royal-Gordon) #5

Just wanted to quickly throw out that Kotlin's Unit class, their "equivalent" of void, semantically follows the definition of "type with only one value" laid out in the enum Void example above.

`enum Void {}` is not a type with only one value; it's a type with no values.

···

--
Brent Royal-Gordon
Architechies