[Accepted] SE-0155: Normalize Enum Case Representation


(John McCall) #1

Proposal Link: https://github.com/apple/swift-evolution/blob/master/proposals/0155-normalize-enum-case-representation.md

Hello Swift Community,

The review of SE-0155 "Normalize Enum Case Representation” ran from March 31st through April 10th, 2017. The proposal is accepted with revisions.

Feedback from the community was positive about most aspects of the proposal. However, there was substantial disagreement about the right direction for pattern matching. The core team discussed this issue in depth.

Pattern matching is central to the use of enum types. It's the only way you can use an enum value, besides general operations like passing it to a function or the special affordances for Optionals. Pattern matching is as central to enums as stored property access is to structs, and it's fair to be worried about anything that would make it substantially more onerous. Unconditionally requiring associated-value labels in case patterns would certainly do that, and several members of the core team expressed concern that it would be bad enough to discourage the use of associated-value labels completely — in effect, subverting the entire language feature being proposed.

It is true that including associated-value labels in case patterns does preserve a great deal of information in the source code:

  - This information can usefully contribute to the clarity of the code following the pattern.

  - Hiding this information can lead to bugs that would be self-evident if the case labels were always included. For example, if a case payload included a number of different boolean flags, it would be easy for a pattern to accidentally label them in the wrong order.

  - Finally, this information may be necessary in order to determine which case is being matched, since the proposal adds the ability to distinguish cases purely by the labels on associated values.

However, the core team feels that there are counter-arguments which weaken the force of these considerations:

  - While an associated-value label can indeed contribute to the readability of the pattern, the programmer can also choose a meaningful name to bind to the associated value. This binding name can convey at least as much information as a label would.

  - The risk of mis-labelling an associated value grows as the number of associated values grows. However, very few cases carry a large number of associated values. As the amount of information which the case should carry grows, it becomes more and more interesting to encapsulate that information in its own struct — among other reasons, to avoid the need to revise every matching case-pattern in the program. Furthermore, when a case does carry a significant number of associated values, there is often a positional conventional between them that lowers the risk of re-ordering: for example, the conventional left-then-right ordering of a binary search tree. Therefore this risk is somewhat over-stated, and of course the programmer should remain free to include labels for cases where they feel the risk is significant.

  - It is likely that cases will continue to be predominantly distinguished by their base name alone. Methods are often distinguished by argument labels because the base name identifies an entire class of operation with many possible variants. In contrast, each case of an enum is a kind of data, and its name is conventionally more like the name of a property than the name of a method, and thus likely to be unique among all the cases. Even when cases are distinguished using only associated value labels, it simply means that the corresponding case-patterns must include those labels; we should not feel required to force that burden on all other case-patterns purely to achieve consistency with this presumably-unusual style.

Accordingly, while it needs to be possible to include associated value labels in a case-pattern, and in some situations it may be wise to include them, the core team believes that requiring associated value labels would be unduly onerous. Therefore, the core teams revises the proposal as follows:

A case pattern may omit labels for the associated values of a case if there is only one case with the same base name and arity. A pattern must omit all labels if it omits any of them; thus, a case pattern either exactly matches the full name of a case or has no labels at all. For example:

  enum E {
    case often(first: Int, second: Int)
    case lots(first: Int, second: Int)
    case many(value: Int)
    case many(first: Int, second: Int)
    case many(alpha: Int, beta: Int)
    case sometimes(value: Int)
    case sometimes(Int)
  }

  switch e {
  // Valid: the sequence of labels exactly matches a case name.
  case .often(first: let a, second: let b):
    ...

  // Valid: there is only one case with this base name.
  case .lots(let a, let b):
    ...

  // Valid: there is only one case with this base name and payload count.
  case .many(let a):
    ...

  // Invalid: there are multiple cases with this base name and payload count.
  case .many(let a, let b):
    ...

  // Valid: the sequence of labels exactly matches a case name.
  case .many(first: let a, second: let b):
    ...

  // Invalid: includes a label, but not on all of the labelled arguments.
  case .same(alpha: let a, let b):
    ...

  // Valid: the sequence of labels exactly matches a case name (that happens to not provide any labels).
  case .sometimes(let x):
    ...

  // Invalid: includes a label, but there is no matching case.
  case .sometimes(badlabel: let x):
    ...
  }

This only affects case patterns. Constructing a case always requires that any associated value labels in the case name be provided.

A case pattern must include patterns for all associated values of the case, even if the associated value has a default value. We may choose to relax this rule in a future release, or generally provide some sort of "..." syntax for indicating that there are associated values being ignored.

The proposal includes a rule inferring labels in case patterns from binding names. The core team feels that imparting local variable names with this kind of significance would be unprecedented, surprising, and rather "pushy". The goal of this rule is also largely achieved by the new rule allowing labels to be omitted regardless of binding. Accordingly, this rule is struck from the proposal. That said, it would be a good idea for the implementation to warn when a binding name matches the label for a different associated value.

John McCall
Review Manager


SE-0155 Status Update
(Xiaodi Wu) #2

Proposal Link: https://github.com/apple/swift-evolution/blob/
master/proposals/0155-normalize-enum-case-representation.md

Hello Swift Community,

The review of SE-0155 "Normalize Enum Case Representation” ran from March
31st through April 10th, 2017. The proposal is *accepted with revisions*.

Feedback from the community was positive about most aspects of the
proposal. However, there was substantial disagreement about the right
direction for pattern matching. The core team discussed this issue in
depth.

Pattern matching is central to the use of enum types. It's the only way
you can use an enum value, besides general operations like passing it to a
function or the special affordances for Optionals. Pattern matching is as
central to enums as stored property access is to structs, and it's fair to
be worried about anything that would make it substantially more onerous.
Unconditionally requiring associated-value labels in case patterns would
certainly do that, and several members of the core team expressed concern
that it would be bad enough to discourage the use of associated-value
labels completely — in effect, subverting the entire language feature being
proposed.

It is true that including associated-value labels in case patterns does
preserve a great deal of information in the source code:

  - This information can usefully contribute to the clarity of the code
following the pattern.

  - Hiding this information can lead to bugs that would be self-evident if
the case labels were always included. For example, if a case payload
included a number of different boolean flags, it would be easy for a
pattern to accidentally label them in the wrong order.

  - Finally, this information may be necessary in order to determine which
case is being matched, since the proposal adds the ability to distinguish
cases purely by the labels on associated values.

However, the core team feels that there are counter-arguments which weaken
the force of these considerations:

  - While an associated-value label can indeed contribute to the
readability of the pattern, the programmer can also choose a meaningful
name to bind to the associated value. This binding name can convey at
least as much information as a label would.

  - The risk of mis-labelling an associated value grows as the number of
associated values grows. However, very few cases carry a large number of
associated values. As the amount of information which the case should
carry grows, it becomes more and more interesting to encapsulate that
information in its own struct — among other reasons, to avoid the need to
revise every matching case-pattern in the program. Furthermore, when a
case does carry a significant number of associated values, there is often a
positional conventional between them that lowers the risk of re-ordering:
for example, the conventional left-then-right ordering of a binary search
tree. Therefore this risk is somewhat over-stated, and of course the
programmer should remain free to include labels for cases where they feel
the risk is significant.

  - It is likely that cases will continue to be predominantly
distinguished by their base name alone. Methods are often distinguished by
argument labels because the base name identifies an entire class of
operation with many possible variants. In contrast, each case of an enum
is a kind of data, and its name is conventionally more like the name of a
property than the name of a method, and thus likely to be unique among all
the cases. Even when cases *are* distinguished using only associated
value labels, it simply means that the corresponding case-patterns must
include those labels; we should not feel required to force that burden on
all other case-patterns purely to achieve consistency with this
presumably-unusual style.

Accordingly, while it needs to be *possible* to include associated value
labels in a case-pattern, and in some situations it may be *wise* to
include them, the core team believes that requiring associated value labels
would be unduly onerous. Therefore, the core teams revises the proposal as
follows:

A case pattern may omit labels for the associated values of a case if
there is only one case with the same base name and arity. A pattern must
omit all labels if it omits any of them; thus, a case pattern either
exactly matches the full name of a case or has no labels at all. For
example:

  enum E {
    case often(first: Int, second: Int)
    case lots(first: Int, second: Int)
    case many(value: Int)
    case many(first: Int, second: Int)
    case many(alpha: Int, beta: Int)
    case sometimes(value: Int)
    case sometimes(Int)
  }

  switch e {
  // Valid: the sequence of labels exactly matches a case name.
  case .often(first: let a, second: let b):
    ...

  // Valid: there is only one case with this base name.
  case .lots(let a, let b):
    ...

  // Valid: there is only one case with this base name and payload count.
  case .many(let a):
    ...

  // Invalid: there are multiple cases with this base name and payload
count.
  case .many(let a, let b):
    ...

  // Valid: the sequence of labels exactly matches a case name.
  case .many(first: let a, second: let b):
    ...

  // Invalid: includes a label, but not on all of the labelled arguments.
  case .same(alpha: let a, let b):
    ...

  // Valid: the sequence of labels exactly matches a case name (that
happens to not provide any labels).
  case .sometimes(let x):
    ...

  // Invalid: includes a label, but there is no matching case.
  case .sometimes(badlabel: let x):
    ...
  }

This only affects case patterns. Constructing a case always requires that
any associated value labels in the case name be provided.

A case pattern must include patterns for all associated values of the
case, even if the associated value has a default value. We may choose to
relax this rule in a future release, or generally provide some sort of
"..." syntax for indicating that there are associated values being ignored.

I assume that the following is obvious and the core team's intention, but
it bears confirmation and being documented:

enum F {
  case many(first: Int, second: Int)
  case many(Int, Int)
  case withDefaultValue(Int, Int, Int=42)
  case withDefaultValue(first: Int, second: Int, third: Int=42)
  case withDefaultValue(first: Int, second: Int, notThirdToMakeAPoint:
Int=43)
}

switch f {
// Valid, even though multiple cases have this base name and arity,
// because it is an exact match for a case that provides no labels.
case .many(let a, let b):
  break

// Invalid, as case pattern must include even values with default.
case .withDefaultValue(let a, let b):
  break

// Valid, as inclusion doesn't mean binding.
case .withDefaultValue(let a, let b, _):
  break

// Valid, for the same reason as above.
case .withDefaultValue(first: let a, second: let b, third: _):
  break

// Invalid, because the label is part of what's required, even if using `_`,
// since otherwise it could be ambiguous as to which case is being matched.
case .withDefaultValue(first: let a, second: let b, _):
  break
}

The proposal includes a rule inferring labels in case patterns from binding

···

On Thu, Apr 20, 2017 at 3:20 PM, John McCall via swift-evolution < swift-evolution@swift.org> wrote:

names. The core team feels that imparting local variable names with this
kind of significance would be unprecedented, surprising, and rather
"pushy". The goal of this rule is also largely achieved by the new rule
allowing labels to be omitted regardless of binding. Accordingly, this
rule is struck from the proposal. That said, it would be a good idea for
the implementation to warn when a binding name matches the label for a
different associated value.

John McCall
Review Manager

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


(John McCall) #3

Proposal Link: https://github.com/apple/swift-evolution/blob/master/proposals/0155-normalize-enum-case-representation.md

Hello Swift Community,

The review of SE-0155 "Normalize Enum Case Representation” ran from March 31st through April 10th, 2017. The proposal is accepted with revisions.

Feedback from the community was positive about most aspects of the proposal. However, there was substantial disagreement about the right direction for pattern matching. The core team discussed this issue in depth.

Pattern matching is central to the use of enum types. It's the only way you can use an enum value, besides general operations like passing it to a function or the special affordances for Optionals. Pattern matching is as central to enums as stored property access is to structs, and it's fair to be worried about anything that would make it substantially more onerous. Unconditionally requiring associated-value labels in case patterns would certainly do that, and several members of the core team expressed concern that it would be bad enough to discourage the use of associated-value labels completely — in effect, subverting the entire language feature being proposed.

It is true that including associated-value labels in case patterns does preserve a great deal of information in the source code:

  - This information can usefully contribute to the clarity of the code following the pattern.

  - Hiding this information can lead to bugs that would be self-evident if the case labels were always included. For example, if a case payload included a number of different boolean flags, it would be easy for a pattern to accidentally label them in the wrong order.

  - Finally, this information may be necessary in order to determine which case is being matched, since the proposal adds the ability to distinguish cases purely by the labels on associated values.

However, the core team feels that there are counter-arguments which weaken the force of these considerations:

  - While an associated-value label can indeed contribute to the readability of the pattern, the programmer can also choose a meaningful name to bind to the associated value. This binding name can convey at least as much information as a label would.

  - The risk of mis-labelling an associated value grows as the number of associated values grows. However, very few cases carry a large number of associated values. As the amount of information which the case should carry grows, it becomes more and more interesting to encapsulate that information in its own struct — among other reasons, to avoid the need to revise every matching case-pattern in the program. Furthermore, when a case does carry a significant number of associated values, there is often a positional conventional between them that lowers the risk of re-ordering: for example, the conventional left-then-right ordering of a binary search tree. Therefore this risk is somewhat over-stated, and of course the programmer should remain free to include labels for cases where they feel the risk is significant.

  - It is likely that cases will continue to be predominantly distinguished by their base name alone. Methods are often distinguished by argument labels because the base name identifies an entire class of operation with many possible variants. In contrast, each case of an enum is a kind of data, and its name is conventionally more like the name of a property than the name of a method, and thus likely to be unique among all the cases. Even when cases are distinguished using only associated value labels, it simply means that the corresponding case-patterns must include those labels; we should not feel required to force that burden on all other case-patterns purely to achieve consistency with this presumably-unusual style.

Accordingly, while it needs to be possible to include associated value labels in a case-pattern, and in some situations it may be wise to include them, the core team believes that requiring associated value labels would be unduly onerous. Therefore, the core teams revises the proposal as follows:

A case pattern may omit labels for the associated values of a case if there is only one case with the same base name and arity. A pattern must omit all labels if it omits any of them; thus, a case pattern either exactly matches the full name of a case or has no labels at all. For example:

  enum E {
    case often(first: Int, second: Int)
    case lots(first: Int, second: Int)
    case many(value: Int)
    case many(first: Int, second: Int)
    case many(alpha: Int, beta: Int)
    case sometimes(value: Int)
    case sometimes(Int)
  }

  switch e {
  // Valid: the sequence of labels exactly matches a case name.
  case .often(first: let a, second: let b):
    ...

  // Valid: there is only one case with this base name.
  case .lots(let a, let b):
    ...

  // Valid: there is only one case with this base name and payload count.
  case .many(let a):
    ...

  // Invalid: there are multiple cases with this base name and payload count.
  case .many(let a, let b):
    ...

  // Valid: the sequence of labels exactly matches a case name.
  case .many(first: let a, second: let b):
    ...

  // Invalid: includes a label, but not on all of the labelled arguments.
  case .same(alpha: let a, let b):
    ...

  // Valid: the sequence of labels exactly matches a case name (that happens to not provide any labels).
  case .sometimes(let x):
    ...

  // Invalid: includes a label, but there is no matching case.
  case .sometimes(badlabel: let x):
    ...
  }

This only affects case patterns. Constructing a case always requires that any associated value labels in the case name be provided.

A case pattern must include patterns for all associated values of the case, even if the associated value has a default value. We may choose to relax this rule in a future release, or generally provide some sort of "..." syntax for indicating that there are associated values being ignored.

I assume that the following is obvious and the core team's intention, but it bears confirmation and being documented:

enum F {
  case many(first: Int, second: Int)
  case many(Int, Int)
  case withDefaultValue(Int, Int, Int=42)
  case withDefaultValue(first: Int, second: Int, third: Int=42)
  case withDefaultValue(first: Int, second: Int, notThirdToMakeAPoint: Int=43)
}

switch f {
// Valid, even though multiple cases have this base name and arity,
// because it is an exact match for a case that provides no labels.
case .many(let a, let b):
  break

// Invalid, as case pattern must include even values with default.
case .withDefaultValue(let a, let b):
  break

// Valid, as inclusion doesn't mean binding.
case .withDefaultValue(let a, let b, _):
  break

// Valid, for the same reason as above.
case .withDefaultValue(first: let a, second: let b, third: _):
  break

// Invalid, because the label is part of what's required, even if using `_`,
// since otherwise it could be ambiguous as to which case is being matched.
case .withDefaultValue(first: let a, second: let b, _):
  break
}

Precisely on all counts.

// Invalid, because the label is part of what's required, even if using `_`,
// since otherwise it could be ambiguous as to which case is being matched.
case .withDefaultValue(first: let a, second: let b, _):
  break

This would also be invalid even if it weren't ambiguous because you cannot selectively omit labels.

John.

···

On Apr 20, 2017, at 4:39 PM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:
On Thu, Apr 20, 2017 at 3:20 PM, John McCall via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

The proposal includes a rule inferring labels in case patterns from binding names. The core team feels that imparting local variable names with this kind of significance would be unprecedented, surprising, and rather "pushy". The goal of this rule is also largely achieved by the new rule allowing labels to be omitted regardless of binding. Accordingly, this rule is struck from the proposal. That said, it would be a good idea for the implementation to warn when a binding name matches the label for a different associated value.

John McCall
Review Manager

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


(Goffredo Marocchi) #4

One thing I wanted to point out and that was a non trivial issue last year and that the core team did discuss and agreed to revisit (if I remember the thread update by Chris Lattner last year):

Note that since the labels aren't part of a tuple, they no longer participate in type checking, behaving consistently with functions.

let f = Expr.elet // f has type ([(String, Expr)], Expr) -> Expr f([], anExpr) // Okay! f(locals: [], body: anExpr) // Won't compile.

I appreciate effort for consistency, but I hope this is not going to be used against the effort to bring labels in functions/stored closures/callbacks.

Swift, as we discussed last year, made a conscious, intentional effort to double down on the self documentation and call site readability of argument labels in general and the status quo after Swift 3/3.1 is not reflecting that whenever functions are stored in variables and/or passed to other functions as arguments.

I would like not to miss the Swift 4.0 deadline for this and we should discuss it sooner rather than later. I brought this up here because, for consistency, we are doubling down on something we should really be discussing to improve in my opinion.

···

Sent from my iPhone

On 20 Apr 2017, at 21:39, Xiaodi Wu via swift-evolution <swift-evolution@swift.org> wrote:

On Thu, Apr 20, 2017 at 3:20 PM, John McCall via swift-evolution <swift-evolution@swift.org> wrote:
Proposal Link: https://github.com/apple/swift-evolution/blob/master/proposals/0155-normalize-enum-case-representation.md

Hello Swift Community,

The review of SE-0155 "Normalize Enum Case Representation” ran from March 31st through April 10th, 2017. The proposal is accepted with revisions.

Feedback from the community was positive about most aspects of the proposal. However, there was substantial disagreement about the right direction for pattern matching. The core team discussed this issue in depth.

Pattern matching is central to the use of enum types. It's the only way you can use an enum value, besides general operations like passing it to a function or the special affordances for Optionals. Pattern matching is as central to enums as stored property access is to structs, and it's fair to be worried about anything that would make it substantially more onerous. Unconditionally requiring associated-value labels in case patterns would certainly do that, and several members of the core team expressed concern that it would be bad enough to discourage the use of associated-value labels completely — in effect, subverting the entire language feature being proposed.

It is true that including associated-value labels in case patterns does preserve a great deal of information in the source code:

  - This information can usefully contribute to the clarity of the code following the pattern.

  - Hiding this information can lead to bugs that would be self-evident if the case labels were always included. For example, if a case payload included a number of different boolean flags, it would be easy for a pattern to accidentally label them in the wrong order.

  - Finally, this information may be necessary in order to determine which case is being matched, since the proposal adds the ability to distinguish cases purely by the labels on associated values.

However, the core team feels that there are counter-arguments which weaken the force of these considerations:

  - While an associated-value label can indeed contribute to the readability of the pattern, the programmer can also choose a meaningful name to bind to the associated value. This binding name can convey at least as much information as a label would.

  - The risk of mis-labelling an associated value grows as the number of associated values grows. However, very few cases carry a large number of associated values. As the amount of information which the case should carry grows, it becomes more and more interesting to encapsulate that information in its own struct — among other reasons, to avoid the need to revise every matching case-pattern in the program. Furthermore, when a case does carry a significant number of associated values, there is often a positional conventional between them that lowers the risk of re-ordering: for example, the conventional left-then-right ordering of a binary search tree. Therefore this risk is somewhat over-stated, and of course the programmer should remain free to include labels for cases where they feel the risk is significant.

  - It is likely that cases will continue to be predominantly distinguished by their base name alone. Methods are often distinguished by argument labels because the base name identifies an entire class of operation with many possible variants. In contrast, each case of an enum is a kind of data, and its name is conventionally more like the name of a property than the name of a method, and thus likely to be unique among all the cases. Even when cases are distinguished using only associated value labels, it simply means that the corresponding case-patterns must include those labels; we should not feel required to force that burden on all other case-patterns purely to achieve consistency with this presumably-unusual style.

Accordingly, while it needs to be possible to include associated value labels in a case-pattern, and in some situations it may be wise to include them, the core team believes that requiring associated value labels would be unduly onerous. Therefore, the core teams revises the proposal as follows:

A case pattern may omit labels for the associated values of a case if there is only one case with the same base name and arity. A pattern must omit all labels if it omits any of them; thus, a case pattern either exactly matches the full name of a case or has no labels at all. For example:

  enum E {
    case often(first: Int, second: Int)
    case lots(first: Int, second: Int)
    case many(value: Int)
    case many(first: Int, second: Int)
    case many(alpha: Int, beta: Int)
    case sometimes(value: Int)
    case sometimes(Int)
  }

  switch e {
  // Valid: the sequence of labels exactly matches a case name.
  case .often(first: let a, second: let b):
    ...

  // Valid: there is only one case with this base name.
  case .lots(let a, let b):
    ...

  // Valid: there is only one case with this base name and payload count.
  case .many(let a):
    ...

  // Invalid: there are multiple cases with this base name and payload count.
  case .many(let a, let b):
    ...

  // Valid: the sequence of labels exactly matches a case name.
  case .many(first: let a, second: let b):
    ...

  // Invalid: includes a label, but not on all of the labelled arguments.
  case .same(alpha: let a, let b):
    ...

  // Valid: the sequence of labels exactly matches a case name (that happens to not provide any labels).
  case .sometimes(let x):
    ...

  // Invalid: includes a label, but there is no matching case.
  case .sometimes(badlabel: let x):
    ...
  }

This only affects case patterns. Constructing a case always requires that any associated value labels in the case name be provided.

A case pattern must include patterns for all associated values of the case, even if the associated value has a default value. We may choose to relax this rule in a future release, or generally provide some sort of "..." syntax for indicating that there are associated values being ignored.

I assume that the following is obvious and the core team's intention, but it bears confirmation and being documented:

enum F {
  case many(first: Int, second: Int)
  case many(Int, Int)
  case withDefaultValue(Int, Int, Int=42)
  case withDefaultValue(first: Int, second: Int, third: Int=42)
  case withDefaultValue(first: Int, second: Int, notThirdToMakeAPoint: Int=43)
}

switch f {
// Valid, even though multiple cases have this base name and arity,
// because it is an exact match for a case that provides no labels.
case .many(let a, let b):
  break

// Invalid, as case pattern must include even values with default.
case .withDefaultValue(let a, let b):
  break

// Valid, as inclusion doesn't mean binding.
case .withDefaultValue(let a, let b, _):
  break

// Valid, for the same reason as above.
case .withDefaultValue(first: let a, second: let b, third: _):
  break

// Invalid, because the label is part of what's required, even if using `_`,
// since otherwise it could be ambiguous as to which case is being matched.
case .withDefaultValue(first: let a, second: let b, _):
  break
}

The proposal includes a rule inferring labels in case patterns from binding names. The core team feels that imparting local variable names with this kind of significance would be unprecedented, surprising, and rather "pushy". The goal of this rule is also largely achieved by the new rule allowing labels to be omitted regardless of binding. Accordingly, this rule is struck from the proposal. That said, it would be a good idea for the implementation to warn when a binding name matches the label for a different associated value.

John McCall
Review Manager

_______________________________________________
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


(John McCall) #5

One thing I wanted to point out and that was a non trivial issue last year and that the core team did discuss and agreed to revisit (if I remember the thread update by Chris Lattner last year):

Note that since the labels aren't part of a tuple, they no longer participate in type checking, behaving consistently with functions.

let f = Expr.elet // f has type ([(String, Expr)], Expr) -> Expr f([], anExpr) // Okay! f(locals: [], body: anExpr) // Won't compile.

I appreciate effort for consistency, but I hope this is not going to be used against the effort to bring labels in functions/stored closures/callbacks.

It will not. The issues are unrelated. Whatever rules we use for supporting argument labels on indirect calls to functions will also apply to indirect calls to enum case constructors.

John.

···

On Apr 20, 2017, at 5:50 PM, Goffredo Marocchi <panajev@gmail.com> wrote:

Swift, as we discussed last year, made a conscious, intentional effort to double down on the self documentation and call site readability of argument labels in general and the status quo after Swift 3/3.1 is not reflecting that whenever functions are stored in variables and/or passed to other functions as arguments.

I would like not to miss the Swift 4.0 deadline for this and we should discuss it sooner rather than later. I brought this up here because, for consistency, we are doubling down on something we should really be discussing to improve in my opinion.

Sent from my iPhone

On 20 Apr 2017, at 21:39, Xiaodi Wu via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Thu, Apr 20, 2017 at 3:20 PM, John McCall via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
Proposal Link: https://github.com/apple/swift-evolution/blob/master/proposals/0155-normalize-enum-case-representation.md

Hello Swift Community,

The review of SE-0155 "Normalize Enum Case Representation” ran from March 31st through April 10th, 2017. The proposal is accepted with revisions.

Feedback from the community was positive about most aspects of the proposal. However, there was substantial disagreement about the right direction for pattern matching. The core team discussed this issue in depth.

Pattern matching is central to the use of enum types. It's the only way you can use an enum value, besides general operations like passing it to a function or the special affordances for Optionals. Pattern matching is as central to enums as stored property access is to structs, and it's fair to be worried about anything that would make it substantially more onerous. Unconditionally requiring associated-value labels in case patterns would certainly do that, and several members of the core team expressed concern that it would be bad enough to discourage the use of associated-value labels completely — in effect, subverting the entire language feature being proposed.

It is true that including associated-value labels in case patterns does preserve a great deal of information in the source code:

  - This information can usefully contribute to the clarity of the code following the pattern.

  - Hiding this information can lead to bugs that would be self-evident if the case labels were always included. For example, if a case payload included a number of different boolean flags, it would be easy for a pattern to accidentally label them in the wrong order.

  - Finally, this information may be necessary in order to determine which case is being matched, since the proposal adds the ability to distinguish cases purely by the labels on associated values.

However, the core team feels that there are counter-arguments which weaken the force of these considerations:

  - While an associated-value label can indeed contribute to the readability of the pattern, the programmer can also choose a meaningful name to bind to the associated value. This binding name can convey at least as much information as a label would.

  - The risk of mis-labelling an associated value grows as the number of associated values grows. However, very few cases carry a large number of associated values. As the amount of information which the case should carry grows, it becomes more and more interesting to encapsulate that information in its own struct — among other reasons, to avoid the need to revise every matching case-pattern in the program. Furthermore, when a case does carry a significant number of associated values, there is often a positional conventional between them that lowers the risk of re-ordering: for example, the conventional left-then-right ordering of a binary search tree. Therefore this risk is somewhat over-stated, and of course the programmer should remain free to include labels for cases where they feel the risk is significant.

  - It is likely that cases will continue to be predominantly distinguished by their base name alone. Methods are often distinguished by argument labels because the base name identifies an entire class of operation with many possible variants. In contrast, each case of an enum is a kind of data, and its name is conventionally more like the name of a property than the name of a method, and thus likely to be unique among all the cases. Even when cases are distinguished using only associated value labels, it simply means that the corresponding case-patterns must include those labels; we should not feel required to force that burden on all other case-patterns purely to achieve consistency with this presumably-unusual style.

Accordingly, while it needs to be possible to include associated value labels in a case-pattern, and in some situations it may be wise to include them, the core team believes that requiring associated value labels would be unduly onerous. Therefore, the core teams revises the proposal as follows:

A case pattern may omit labels for the associated values of a case if there is only one case with the same base name and arity. A pattern must omit all labels if it omits any of them; thus, a case pattern either exactly matches the full name of a case or has no labels at all. For example:

  enum E {
    case often(first: Int, second: Int)
    case lots(first: Int, second: Int)
    case many(value: Int)
    case many(first: Int, second: Int)
    case many(alpha: Int, beta: Int)
    case sometimes(value: Int)
    case sometimes(Int)
  }

  switch e {
  // Valid: the sequence of labels exactly matches a case name.
  case .often(first: let a, second: let b):
    ...

  // Valid: there is only one case with this base name.
  case .lots(let a, let b):
    ...

  // Valid: there is only one case with this base name and payload count.
  case .many(let a):
    ...

  // Invalid: there are multiple cases with this base name and payload count.
  case .many(let a, let b):
    ...

  // Valid: the sequence of labels exactly matches a case name.
  case .many(first: let a, second: let b):
    ...

  // Invalid: includes a label, but not on all of the labelled arguments.
  case .same(alpha: let a, let b):
    ...

  // Valid: the sequence of labels exactly matches a case name (that happens to not provide any labels).
  case .sometimes(let x):
    ...

  // Invalid: includes a label, but there is no matching case.
  case .sometimes(badlabel: let x):
    ...
  }

This only affects case patterns. Constructing a case always requires that any associated value labels in the case name be provided.

A case pattern must include patterns for all associated values of the case, even if the associated value has a default value. We may choose to relax this rule in a future release, or generally provide some sort of "..." syntax for indicating that there are associated values being ignored.

I assume that the following is obvious and the core team's intention, but it bears confirmation and being documented:

enum F {
  case many(first: Int, second: Int)
  case many(Int, Int)
  case withDefaultValue(Int, Int, Int=42)
  case withDefaultValue(first: Int, second: Int, third: Int=42)
  case withDefaultValue(first: Int, second: Int, notThirdToMakeAPoint: Int=43)
}

switch f {
// Valid, even though multiple cases have this base name and arity,
// because it is an exact match for a case that provides no labels.
case .many(let a, let b):
  break

// Invalid, as case pattern must include even values with default.
case .withDefaultValue(let a, let b):
  break

// Valid, as inclusion doesn't mean binding.
case .withDefaultValue(let a, let b, _):
  break

// Valid, for the same reason as above.
case .withDefaultValue(first: let a, second: let b, third: _):
  break

// Invalid, because the label is part of what's required, even if using `_`,
// since otherwise it could be ambiguous as to which case is being matched.
case .withDefaultValue(first: let a, second: let b, _):
  break
}

The proposal includes a rule inferring labels in case patterns from binding names. The core team feels that imparting local variable names with this kind of significance would be unprecedented, surprising, and rather "pushy". The goal of this rule is also largely achieved by the new rule allowing labels to be omitted regardless of binding. Accordingly, this rule is struck from the proposal. That said, it would be a good idea for the implementation to warn when a binding name matches the label for a different associated value.

John McCall
Review Manager

_______________________________________________
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


(Goffredo Marocchi) #6

One thing I wanted to point out and that was a non trivial issue last year and that the core team did discuss and agreed to revisit (if I remember the thread update by Chris Lattner last year):

Note that since the labels aren't part of a tuple, they no longer participate in type checking, behaving consistently with functions.

let f = Expr.elet // f has type ([(String, Expr)], Expr) -> Expr f([], anExpr) // Okay! f(locals: [], body: anExpr) // Won't compile.

I appreciate effort for consistency, but I hope this is not going to be used against the effort to bring labels in functions/stored closures/callbacks.

It will not. The issues are unrelated. Whatever rules we use for supporting argument labels on indirect calls to functions will also apply to indirect calls to enum case constructors.

Thanks for your reply :).

···

Sent from my iPhone

On 20 Apr 2017, at 23:15, John McCall <rjmccall@apple.com> wrote:

On Apr 20, 2017, at 5:50 PM, Goffredo Marocchi <panajev@gmail.com> wrote:

John.

Swift, as we discussed last year, made a conscious, intentional effort to double down on the self documentation and call site readability of argument labels in general and the status quo after Swift 3/3.1 is not reflecting that whenever functions are stored in variables and/or passed to other functions as arguments.

I would like not to miss the Swift 4.0 deadline for this and we should discuss it sooner rather than later. I brought this up here because, for consistency, we are doubling down on something we should really be discussing to improve in my opinion.

Sent from my iPhone

On 20 Apr 2017, at 21:39, Xiaodi Wu via swift-evolution <swift-evolution@swift.org> wrote:

On Thu, Apr 20, 2017 at 3:20 PM, John McCall via swift-evolution <swift-evolution@swift.org> wrote:
Proposal Link: https://github.com/apple/swift-evolution/blob/master/proposals/0155-normalize-enum-case-representation.md

Hello Swift Community,

The review of SE-0155 "Normalize Enum Case Representation” ran from March 31st through April 10th, 2017. The proposal is accepted with revisions.

Feedback from the community was positive about most aspects of the proposal. However, there was substantial disagreement about the right direction for pattern matching. The core team discussed this issue in depth.

Pattern matching is central to the use of enum types. It's the only way you can use an enum value, besides general operations like passing it to a function or the special affordances for Optionals. Pattern matching is as central to enums as stored property access is to structs, and it's fair to be worried about anything that would make it substantially more onerous. Unconditionally requiring associated-value labels in case patterns would certainly do that, and several members of the core team expressed concern that it would be bad enough to discourage the use of associated-value labels completely — in effect, subverting the entire language feature being proposed.

It is true that including associated-value labels in case patterns does preserve a great deal of information in the source code:

  - This information can usefully contribute to the clarity of the code following the pattern.

  - Hiding this information can lead to bugs that would be self-evident if the case labels were always included. For example, if a case payload included a number of different boolean flags, it would be easy for a pattern to accidentally label them in the wrong order.

  - Finally, this information may be necessary in order to determine which case is being matched, since the proposal adds the ability to distinguish cases purely by the labels on associated values.

However, the core team feels that there are counter-arguments which weaken the force of these considerations:

  - While an associated-value label can indeed contribute to the readability of the pattern, the programmer can also choose a meaningful name to bind to the associated value. This binding name can convey at least as much information as a label would.

  - The risk of mis-labelling an associated value grows as the number of associated values grows. However, very few cases carry a large number of associated values. As the amount of information which the case should carry grows, it becomes more and more interesting to encapsulate that information in its own struct — among other reasons, to avoid the need to revise every matching case-pattern in the program. Furthermore, when a case does carry a significant number of associated values, there is often a positional conventional between them that lowers the risk of re-ordering: for example, the conventional left-then-right ordering of a binary search tree. Therefore this risk is somewhat over-stated, and of course the programmer should remain free to include labels for cases where they feel the risk is significant.

  - It is likely that cases will continue to be predominantly distinguished by their base name alone. Methods are often distinguished by argument labels because the base name identifies an entire class of operation with many possible variants. In contrast, each case of an enum is a kind of data, and its name is conventionally more like the name of a property than the name of a method, and thus likely to be unique among all the cases. Even when cases are distinguished using only associated value labels, it simply means that the corresponding case-patterns must include those labels; we should not feel required to force that burden on all other case-patterns purely to achieve consistency with this presumably-unusual style.

Accordingly, while it needs to be possible to include associated value labels in a case-pattern, and in some situations it may be wise to include them, the core team believes that requiring associated value labels would be unduly onerous. Therefore, the core teams revises the proposal as follows:

A case pattern may omit labels for the associated values of a case if there is only one case with the same base name and arity. A pattern must omit all labels if it omits any of them; thus, a case pattern either exactly matches the full name of a case or has no labels at all. For example:

  enum E {
    case often(first: Int, second: Int)
    case lots(first: Int, second: Int)
    case many(value: Int)
    case many(first: Int, second: Int)
    case many(alpha: Int, beta: Int)
    case sometimes(value: Int)
    case sometimes(Int)
  }

  switch e {
  // Valid: the sequence of labels exactly matches a case name.
  case .often(first: let a, second: let b):
    ...

  // Valid: there is only one case with this base name.
  case .lots(let a, let b):
    ...

  // Valid: there is only one case with this base name and payload count.
  case .many(let a):
    ...

  // Invalid: there are multiple cases with this base name and payload count.
  case .many(let a, let b):
    ...

  // Valid: the sequence of labels exactly matches a case name.
  case .many(first: let a, second: let b):
    ...

  // Invalid: includes a label, but not on all of the labelled arguments.
  case .same(alpha: let a, let b):
    ...

  // Valid: the sequence of labels exactly matches a case name (that happens to not provide any labels).
  case .sometimes(let x):
    ...

  // Invalid: includes a label, but there is no matching case.
  case .sometimes(badlabel: let x):
    ...
  }

This only affects case patterns. Constructing a case always requires that any associated value labels in the case name be provided.

A case pattern must include patterns for all associated values of the case, even if the associated value has a default value. We may choose to relax this rule in a future release, or generally provide some sort of "..." syntax for indicating that there are associated values being ignored.

I assume that the following is obvious and the core team's intention, but it bears confirmation and being documented:

enum F {
  case many(first: Int, second: Int)
  case many(Int, Int)
  case withDefaultValue(Int, Int, Int=42)
  case withDefaultValue(first: Int, second: Int, third: Int=42)
  case withDefaultValue(first: Int, second: Int, notThirdToMakeAPoint: Int=43)
}

switch f {
// Valid, even though multiple cases have this base name and arity,
// because it is an exact match for a case that provides no labels.
case .many(let a, let b):
  break

// Invalid, as case pattern must include even values with default.
case .withDefaultValue(let a, let b):
  break

// Valid, as inclusion doesn't mean binding.
case .withDefaultValue(let a, let b, _):
  break

// Valid, for the same reason as above.
case .withDefaultValue(first: let a, second: let b, third: _):
  break

// Invalid, because the label is part of what's required, even if using `_`,
// since otherwise it could be ambiguous as to which case is being matched.
case .withDefaultValue(first: let a, second: let b, _):
  break
}

The proposal includes a rule inferring labels in case patterns from binding names. The core team feels that imparting local variable names with this kind of significance would be unprecedented, surprising, and rather "pushy". The goal of this rule is also largely achieved by the new rule allowing labels to be omitted regardless of binding. Accordingly, this rule is struck from the proposal. That said, it would be a good idea for the implementation to warn when a binding name matches the label for a different associated value.

John McCall
Review Manager

_______________________________________________
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