[Pitch] Small bit of sugar for enum case with Void associated value

Currently if you have the following enum:

enum E<T> {
  case c(T)
}

then if T is Void, you have to write one of the following:

let x: E<Void> = .c(Void())
let y: E<Void> = .c(())

Looks awkward, no? In this case you can omit `<Void>` after `E` because it can be inferred, but if writing a (non-generic) function taking an argument of type `E<Void>`, then the `<Void>` cannot be omitted, and you still have to write `.c(())` for the case name.

I’m proposing that for enum cases with a single associated value of Void type, or of a generic type that is equal to Void in some instance, you may omit the parentheses altogether and merely write

let x: E<Void> = .c

The rationale is twofold: first, double parentheses just looks bad; second, there is only a single value of type Void, which means the associated value of `.c` is trivially inferable, and hence should be omissible.

I am not proposing that a bare `E.c` imply a type of `E<Void>` — `E.c` should still be illegal in the absence of specification of the generic type — only that when the type is known to be `E<Void>`, `.c` can replace `.c(())`.

Thoughts?

My first response is +1

My second response is to ask just how much would it break things to expand this and allow omitting the argument anywhere its type is known to be Void? Maybe by implicitly providing a default value of `()` wherever there's a `Void` argument? Like make `func foo(x: Void) {...}` implicitly become `func foo(x: Void = ()) {...}`? I have a sneaking suspicion that this question's already been asked, but I'm not sure.

- Dave Sweeris

···

On Jul 25, 2017, at 12:38, Robert Bennett via swift-evolution <swift-evolution@swift.org> wrote:

Currently if you have the following enum:

enum E<T> {
  case c(T)
}

then if T is Void, you have to write one of the following:

let x: E<Void> = .c(Void())
let y: E<Void> = .c(())

Looks awkward, no? In this case you can omit `<Void>` after `E` because it can be inferred, but if writing a (non-generic) function taking an argument of type `E<Void>`, then the `<Void>` cannot be omitted, and you still have to write `.c(())` for the case name.

I’m proposing that for enum cases with a single associated value of Void type, or of a generic type that is equal to Void in some instance, you may omit the parentheses altogether and merely write

let x: E<Void> = .c

The rationale is twofold: first, double parentheses just looks bad; second, there is only a single value of type Void, which means the associated value of `.c` is trivially inferable, and hence should be omissible.

I am not proposing that a bare `E.c` imply a type of `E<Void>` — `E.c` should still be illegal in the absence of specification of the generic type — only that when the type is known to be `E<Void>`, `.c` can replace `.c(())`.

Thoughts?

Currently if you have the following enum:

enum E<T> {
  case c(T)
}

then if T is Void, you have to write one of the following:

let x: E<Void> = .c(Void())
let y: E<Void> = .c(())

Looks awkward, no? In this case you can omit `<Void>` after `E` because it can be inferred, but if writing a (non-generic) function taking an argument of type `E<Void>`, then the `<Void>` cannot be omitted, and you still have to write `.c(())` for the case name.

I’m proposing that for enum cases with a single associated value of Void type, or of a generic type that is equal to Void in some instance, you may omit the parentheses altogether and merely write

let x: E<Void> = .c

The rationale is twofold: first, double parentheses just looks bad; second, there is only a single value of type Void, which means the associated value of `.c` is trivially inferable, and hence should be omissible.

I am not proposing that a bare `E.c` imply a type of `E<Void>` — `E.c` should still be illegal in the absence of specification of the generic type — only that when the type is known to be `E<Void>`, `.c` can replace `.c(())`.

Thoughts?

My first response is +1

My second response is to ask just how much would it break things to expand this and allow omitting the argument anywhere its type is known to be Void? Maybe by implicitly providing a default value of `()` wherever there's a `Void` argument? Like make `func foo(x: Void) {...}` implicitly become `func foo(x: Void = ()) {...}`? I have a sneaking suspicion that this question's already been asked, but I'm not sure.

I think default arguments for associated values have come up before. I would love to see that added someday.

···

On Jul 25, 2017, at 3:26 PM, David Sweeris via swift-evolution <swift-evolution@swift.org> wrote:
On Jul 25, 2017, at 12:38, Robert Bennett via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

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

Yes, I discussed this some time back. It breaks some things with overloads,
if I recall, but off the top of my head not recalling what.

At some point, I also suggested regarding any case without associated
values as equivalent to a case with an associated value of type Void. This
was never adopted. There _is_ an underlying issue with that idea, as the
property “foo” is not the same as the method “foo(_: Void)”, and it is even
permitted to have both of these on the same type. The purpose of the most
recent Swift proposal on enum cases was to align the syntax with functions
as closely as possible; if overloading the base name of a case (as proposed
back then to favorable reviews) is ever allowed, then the same will
naturally be possible with enums (that is, a case named “foo” and another
named “foo(_: Void)” in the same type).

In the case of concrete enums, a case with associated value of type Void
can be given a default value once they are supported (already approved);
the trouble here is that in the generic case presented here the same is not
possible. It is unclear to me whether this special rule would see broad
use, as it appears to come into play *only* for a generic enum. Moreover,
in the one scenario I can think of, it reads cryptically: “let foo =
Optional.some” would then be allowed (while “let bar = Optional.none” still
would not). The upside is that it avoids having to write four or six
letters (“Optional.some(Void())” is never necessary, as it’s also just
“Optional.some(Void)”, and that reads acceptably in my view).

···

On Tue, Jul 25, 2017 at 15:26 David Sweeris via swift-evolution < swift-evolution@swift.org> wrote:

On Jul 25, 2017, at 12:38, Robert Bennett via swift-evolution < > swift-evolution@swift.org> wrote:

Currently if you have the following enum:

enum E<T> {
case c(T)
}

then if T is Void, you have to write one of the following:

let x: E<Void> = .c(Void())
let y: E<Void> = .c(())

Looks awkward, no? In this case you can omit `<Void>` after `E` because it
can be inferred, but if writing a (non-generic) function taking an argument
of type `E<Void>`, then the `<Void>` cannot be omitted, and you still have
to write `.c(())` for the case name.

I’m proposing that for enum cases with a single associated value of Void
type, or of a generic type that is equal to Void in some instance, you may
omit the parentheses altogether and merely write

let x: E<Void> = .c

The rationale is twofold: first, double parentheses just looks bad;
second, there is only a single value of type Void, which means the
associated value of `.c` is trivially inferable, and hence should be
omissible.

I am not proposing that a bare `E.c` imply a type of `E<Void>` — `E.c`
should still be illegal in the absence of specification of the generic type
— only that when the type is known to be `E<Void>`, `.c` can replace
`.c(())`.

Thoughts?

My first response is +1

My second response is to ask just how much would it break things to expand
this and allow omitting the argument anywhere its type is known to be Void?
Maybe by implicitly providing a default value of `()` wherever there's a
`Void` argument? Like make `func foo(x: Void) {...}` implicitly become `func
foo(x: Void = ()) {...}`? I have a sneaking suspicion that this
question's already been asked, but I'm not sure.

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

+1 to this

I not only support the case in question, but I 100% support any proposal that would push to compiler to completely remove any distinction from isomorphic tuples, by always reducing the tuple to the simplest form (this kind of discussion also took place in the thread about the SE110 rollback).

In case of:

enum Foo<T> {
  case bar(T)
  case baz()
}

if Foo<T>.bar is to be considered a function of type "(T) -> Foo<T>", and Foo<T>.baz a function of type "() -> Foo<T>", then when "T == ()" the two function must be considered of the same type, and there's no real reason from the user standpoint to consider them of different type.

And not just that, because in the case of:

enum Foo<T> {
  case bar(Int,T)
  case baz()
}

if "T == ()" the two functions should be respectively of type "(Int) -> Foo<()>" and "() -> Foo<()>", while the first one is currently of type "(Int,()) -> Foo<()>" which makes absolutely no sense.

A function that has a value of type "()" in the inputs makes no sense at all. The following function signature is meaningless and its avoidance should be enforced by the compiler

func wat(_ value: ()) -> Int {
  return 42
}

let a = wat

let b = a() /// compiler error

The empty tuple should be automatically removed by the compiler for generics, or an error should be emitted in non-generic cases, simply because it is a singleton and it can always be produced out of thin air. It shouldn't be a valid argument of a function, and it should be stripped by any tuple like "(A,(),B)" or similar.

I made several more points in the SE110 discussion, and answered to most of the objections, so if you're interested you can read my answers there.

Thanks

Elviro

Functions that are overloaded with one version taking no arguments and another taking only some number of Void arguments would break, but I'm not sure how important that is. Nor am I sure that's all that would break. I'm mean, really we'd just be automatically adding functions that only have Void arguments to the call-site "func foo() {}" vs "func foo(x: SomeType = someValue) {}" issue.

Meh, maybe such implicit behavior should be reserved for "Never", if we ever get that.

- Dave Sweeris

···

On Jul 25, 2017, at 2:20 PM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

Yes, I discussed this some time back. It breaks some things with overloads, if I recall, but off the top of my head not recalling what.

Optional.some(Void) gives me "Expected member name or constructor call after type name”.

And as I said, the compiler should not be allowed to use the absence of parentheses to infer a Void generic type; only when the generic type is known to be Void may the parentheses be omitted. Under this proposal, Optional.some would not be allowed.

The scenario I’m working with, which inspired this thought, is roughly:

enum Result<T> {
  case success(T)
  case failure(Error)
}

func doSomething(completion: @escaping (Result<Void>)->()) { /* At some point, call completion(.success(())) */ }

The idea is that in case of success, I merely want to indicate success, whereas in case of an error I want to pass the error through to the completion. This requires calling `completion(.success(()))`. I don’t think I should have to make another enum with the same cases but no associated value for success (and no generic type) to make this work nicely — that makes things brittle in case I ever want to replace Void with a different type.

···

On Jul 25, 2017, at 5:20 PM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

Yes, I discussed this some time back. It breaks some things with overloads, if I recall, but off the top of my head not recalling what.

At some point, I also suggested regarding any case without associated values as equivalent to a case with an associated value of type Void. This was never adopted. There _is_ an underlying issue with that idea, as the property “foo” is not the same as the method “foo(_: Void)”, and it is even permitted to have both of these on the same type. The purpose of the most recent Swift proposal on enum cases was to align the syntax with functions as closely as possible; if overloading the base name of a case (as proposed back then to favorable reviews) is ever allowed, then the same will naturally be possible with enums (that is, a case named “foo” and another named “foo(_: Void)” in the same type).

In the case of concrete enums, a case with associated value of type Void can be given a default value once they are supported (already approved); the trouble here is that in the generic case presented here the same is not possible. It is unclear to me whether this special rule would see broad use, as it appears to come into play *only* for a generic enum. Moreover, in the one scenario I can think of, it reads cryptically: “let foo = Optional.some” would then be allowed (while “let bar = Optional.none” still would not). The upside is that it avoids having to write four or six letters (“Optional.some(Void())” is never necessary, as it’s also just “Optional.some(Void)”, and that reads acceptably in my view).
On Tue, Jul 25, 2017 at 15:26 David Sweeris via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Jul 25, 2017, at 12:38, Robert Bennett via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Currently if you have the following enum:

enum E<T> {
  case c(T)
}

then if T is Void, you have to write one of the following:

let x: E<Void> = .c(Void())
let y: E<Void> = .c(())

Looks awkward, no? In this case you can omit `<Void>` after `E` because it can be inferred, but if writing a (non-generic) function taking an argument of type `E<Void>`, then the `<Void>` cannot be omitted, and you still have to write `.c(())` for the case name.

I’m proposing that for enum cases with a single associated value of Void type, or of a generic type that is equal to Void in some instance, you may omit the parentheses altogether and merely write

let x: E<Void> = .c

The rationale is twofold: first, double parentheses just looks bad; second, there is only a single value of type Void, which means the associated value of `.c` is trivially inferable, and hence should be omissible.

I am not proposing that a bare `E.c` imply a type of `E<Void>` — `E.c` should still be illegal in the absence of specification of the generic type — only that when the type is known to be `E<Void>`, `.c` can replace `.c(())`.

Thoughts?

My first response is +1

My second response is to ask just how much would it break things to expand this and allow omitting the argument anywhere its type is known to be Void? Maybe by implicitly providing a default value of `()` wherever there's a `Void` argument? Like make `func foo(x: Void) {...}` implicitly become `func foo(x: Void = ()) {...}`? I have a sneaking suspicion that this question's already been asked, but I'm not sure.

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

Default values for associated types are approved for Swift 4. They just
haven’t been implemented.

···

On Tue, Jul 25, 2017 at 16:02 Matthew Johnson via swift-evolution < swift-evolution@swift.org> wrote:

On Jul 25, 2017, at 3:26 PM, David Sweeris via swift-evolution < > swift-evolution@swift.org> wrote:

On Jul 25, 2017, at 12:38, Robert Bennett via swift-evolution < > swift-evolution@swift.org> wrote:

Currently if you have the following enum:

enum E<T> {
case c(T)
}

then if T is Void, you have to write one of the following:

let x: E<Void> = .c(Void())
let y: E<Void> = .c(())

Looks awkward, no? In this case you can omit `<Void>` after `E` because it
can be inferred, but if writing a (non-generic) function taking an argument
of type `E<Void>`, then the `<Void>` cannot be omitted, and you still have
to write `.c(())` for the case name.

I’m proposing that for enum cases with a single associated value of Void
type, or of a generic type that is equal to Void in some instance, you may
omit the parentheses altogether and merely write

let x: E<Void> = .c

The rationale is twofold: first, double parentheses just looks bad;
second, there is only a single value of type Void, which means the
associated value of `.c` is trivially inferable, and hence should be
omissible.

I am not proposing that a bare `E.c` imply a type of `E<Void>` — `E.c`
should still be illegal in the absence of specification of the generic type
— only that when the type is known to be `E<Void>`, `.c` can replace
`.c(())`.

Thoughts?

My first response is +1

My second response is to ask just how much would it break things to expand
this and allow omitting the argument anywhere its type is known to be Void?
Maybe by implicitly providing a default value of `()` wherever there's a
`Void` argument? Like make `func foo(x: Void) {...}` implicitly become `func
foo(x: Void = ()) {...}`? I have a sneaking suspicion that this
question's already been asked, but I'm not sure.

I think default arguments for associated values have come up before. I
would love to see that added someday.

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

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

Optional.some(Void) gives me "Expected member name or constructor call
after type name”.

That would appear to be straightforwardly a compiler bug. For instance, the
following compiles:

let _Void = Void
let foo = Optional.some(_Void)
// I'll discuss the warnings you see in a bit, below.

In addition, the following compiles just fine, which is the crux of your
use case:

enum Foo<T> {
    case bar(T)
    case baz
}

let x = Foo.bar(Void)

And as I said, the compiler should not be allowed to use the absence of

parentheses to infer a Void generic type; only when the generic type is
known to be Void may the parentheses be omitted. Under this proposal,
Optional.some would not be allowed.

There is no precedent for such a rule in Swift. Inferring Void is allowed
but results in a warning under certain circumstances, but it is not
forbidden. This has been discussed on the list before and it is not an
oversight that it's a warning and not an error, and that the warning is
about storing the initialized result and not about inferring the type.

To be clear, if the rule you propose is permitted, `Optional.some` *would*
be inferred to be `Optional.some(Void)` unless type inference rules are
revisited (which, I would argue, is entirely overkill in terms of the scope
of that undertaking as compared to the magnitude of the issue being
addressed). Instead, there would be a warning if you tried to bind
`Optional.some` to a variable, but you would absolutely be allowed to pass
it to a function: `callSomeOtherFunction(Optional.some!)` would be an
alternative spelling for `callSomeOtherFunction(Void)`; effectively,
`Optional.some!` would become a synonym for `Void` under certain
circumstances but not others.

The scenario I’m working with, which inspired this thought, is roughly:

enum Result<T> {
case success(T)
case failure(Error)
}

func doSomething(completion: @escaping (Result<Void>)->()) { /* At some
point, call completion(.success(())) */ }

The idea is that in case of success, I merely want to indicate success,
whereas in case of an error I want to pass the error through to the
completion. This requires calling `completion(.success(()))`.

This is, as far as I can tell, essentially the same use case that SE-0110
rendered problematic. As the core team wrote in their decision there, it's
clear that Swift doesn't have a good story for how to support certain
coding styles that involve Void arguments. For the moment, a limited and
temporary exception was approved to roll back parts of SE-0110, but the
idea is that all of this needs to be revisited.

I'd agree that your use case here is a good example of something that
should be taken into account as part of that re-examination, but IMO
inventing one or several new rules specific to generic enums cannot be the
way to go--especially since it is certain that SE-0110 and related issues
*will* be revisited and implicit or explicit tuple splatting will be given
great consideration. By that time, implementation of the existing and
approved proposal on enums will mean that the exact same issue will be
addressed automatically for enum cases.

I don’t think I should have to make another enum with the same cases but no

associated value for success (and no generic type) to make this work nicely
— that makes things brittle in case I ever want to replace Void with a
different type.

If I understand Swift's error handling rationale correctly, a Result type
with no associated value for success would be, _by design_, an
Optional<Error>. Why wouldn't you use that in this scenario? (Well,
actually, if I understand Swift's error handling rationale, the intended
way to spell this in Swift is `throws -> Void` (aka `throws`)).

···

On Tue, Jul 25, 2017 at 16:35 Robert Bennett <rltbennett@icloud.com> wrote:

On Jul 25, 2017, at 5:20 PM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

Yes, I discussed this some time back. It breaks some things with
overloads, if I recall, but off the top of my head not recalling what.

At some point, I also suggested regarding any case without associated
values as equivalent to a case with an associated value of type Void. This
was never adopted. There _is_ an underlying issue with that idea, as the
property “foo” is not the same as the method “foo(_: Void)”, and it is even
permitted to have both of these on the same type. The purpose of the most
recent Swift proposal on enum cases was to align the syntax with functions
as closely as possible; if overloading the base name of a case (as proposed
back then to favorable reviews) is ever allowed, then the same will
naturally be possible with enums (that is, a case named “foo” and another
named “foo(_: Void)” in the same type).

In the case of concrete enums, a case with associated value of type Void
can be given a default value once they are supported (already approved);
the trouble here is that in the generic case presented here the same is not
possible. It is unclear to me whether this special rule would see broad
use, as it appears to come into play *only* for a generic enum. Moreover,
in the one scenario I can think of, it reads cryptically: “let foo =
Optional.some” would then be allowed (while “let bar = Optional.none” still
would not). The upside is that it avoids having to write four or six
letters (“Optional.some(Void())” is never necessary, as it’s also just
“Optional.some(Void)”, and that reads acceptably in my view).
On Tue, Jul 25, 2017 at 15:26 David Sweeris via swift-evolution < > swift-evolution@swift.org> wrote:

On Jul 25, 2017, at 12:38, Robert Bennett via swift-evolution < >> swift-evolution@swift.org> wrote:

Currently if you have the following enum:

enum E<T> {
case c(T)
}

then if T is Void, you have to write one of the following:

let x: E<Void> = .c(Void())
let y: E<Void> = .c(())

Looks awkward, no? In this case you can omit `<Void>` after `E` because
it can be inferred, but if writing a (non-generic) function taking an
argument of type `E<Void>`, then the `<Void>` cannot be omitted, and you
still have to write `.c(())` for the case name.

I’m proposing that for enum cases with a single associated value of Void
type, or of a generic type that is equal to Void in some instance, you may
omit the parentheses altogether and merely write

let x: E<Void> = .c

The rationale is twofold: first, double parentheses just looks bad;
second, there is only a single value of type Void, which means the
associated value of `.c` is trivially inferable, and hence should be
omissible.

I am not proposing that a bare `E.c` imply a type of `E<Void>` — `E.c`
should still be illegal in the absence of specification of the generic type
— only that when the type is known to be `E<Void>`, `.c` can replace
`.c(())`.

Thoughts?

My first response is +1

My second response is to ask just how much would it break things to
expand this and allow omitting the argument anywhere its type is known to
be Void? Maybe by implicitly providing a default value of `()` wherever
there's a `Void` argument? Like make `func foo(x: Void) {...}` implicitly
become `func foo(x: Void = ()) {...}`? I have a sneaking suspicion that
this question's already been asked, but I'm not sure.

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

Default values for associated types are approved for Swift 4. They just haven’t been implemented.

Ahh, I had forgotten that. Thanks for clarifying! :slight_smile:

···

On Jul 25, 2017, at 4:02 PM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

On Tue, Jul 25, 2017 at 16:02 Matthew Johnson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Jul 25, 2017, at 3:26 PM, David Sweeris via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Jul 25, 2017, at 12:38, Robert Bennett via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Currently if you have the following enum:

enum E<T> {
  case c(T)
}

then if T is Void, you have to write one of the following:

let x: E<Void> = .c(Void())
let y: E<Void> = .c(())

Looks awkward, no? In this case you can omit `<Void>` after `E` because it can be inferred, but if writing a (non-generic) function taking an argument of type `E<Void>`, then the `<Void>` cannot be omitted, and you still have to write `.c(())` for the case name.

I’m proposing that for enum cases with a single associated value of Void type, or of a generic type that is equal to Void in some instance, you may omit the parentheses altogether and merely write

let x: E<Void> = .c

The rationale is twofold: first, double parentheses just looks bad; second, there is only a single value of type Void, which means the associated value of `.c` is trivially inferable, and hence should be omissible.

I am not proposing that a bare `E.c` imply a type of `E<Void>` — `E.c` should still be illegal in the absence of specification of the generic type — only that when the type is known to be `E<Void>`, `.c` can replace `.c(())`.

Thoughts?

My first response is +1

My second response is to ask just how much would it break things to expand this and allow omitting the argument anywhere its type is known to be Void? Maybe by implicitly providing a default value of `()` wherever there's a `Void` argument? Like make `func foo(x: Void) {...}` implicitly become `func foo(x: Void = ()) {...}`? I have a sneaking suspicion that this question's already been asked, but I'm not sure.

I think default arguments for associated values have come up before. I would love to see that added someday.

- Dave Sweeris
_______________________________________________
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

Terms of Service

Privacy Policy

Cookie Policy