Consolidate Code for Each Case in Enum

I think you have correctly summarized the proposal.

The complexity can't be gotten rid of (except maybe with macros... depending on the details, of course). Sometimes viewing the same thing from a different angle can be helpful. As long as this syntax is "in addition to" and not "instead of", the only downside I can see to allowing this is a bit of "language bloat", which doesn't particularly bother me. I don't know how hard it would be for the compiler to sort out which "mode" it should parse the enum in, though, especially since you might want to write some parts in the regular way and some parts in this new way.

So... I guess I'm +1, pending someone who knows more about compiler internals weighing in on implementation implications.

- Dave Sweeris

···

On Jan 11, 2017, at 08:48, Derrick Ho via swift-evolution <swift-evolution@swift.org> wrote:

Interesting proposal Tim. So instead of repeating the enum cases multiple times we repeat the functions multiple times?

I feel like this merely flipping the complexity but not really getting rid of it.

Wouldn’t protocols be a better solution in this case? If little to no logic can be shared between enum cases, why have the enum in the first place?

Your variant:

protocol State {

    mutating func react(to event: Event)
}

enum AuthenticationState: State, CustomStringConvertible {

    case invalid {
        var description: String { return "Authentication invalid." }

        mutating func react(to event: Event) {
            switch event {

            case let login as UserLoggedIn:
                self = .validated(token: login.token)
            default:
                break
            }
        }
    }

    case expired(_ expiration: Date) {
        var description: String { return "Authentication expired at \(expiration)." }

        mutating func react(to event: Event) {
            switch event {
            case let refreshed as TokenRefreshed:

                self = .validated(token: refreshed.token)

            default:
                break
            }
        }
    }

    case validated(token: String) {

        var description: String { return "The authentication token is \(token)." }

        mutating func react(to event: Event) {
            switch event {

            case let expiration as TokenExpired:
                print("Expiring token: \(token)")
                self = .expired(expiration.date)
            case _ as TokenRejected:
                self = .invalid
            case _ as UserLoggedOut:
                self = .invalid
            default:
                break
            }
        }
    }

}
My suggestion:

public protocol State {

    mutating func react(to event: Event)
}

@closed protocol AuthenticationState : State, CustomStringConvertible { }

struct InvalidAuthenticationState : AuthenticationState {
    var description: String { return "Authentication invalid." }

    mutating func react(to event: Event) {
        switch event {

        case let login as UserLoggedIn:
            self = .validated(token: login.token)
        default:
            break
        }
    }
}

struct ExpiredAuthenticationState : AuthenticationState {
    var expiration: Date

    var description: String { return "Authentication expired at \(expiration)." }

    mutating func react(to event: Event) {
        switch event {
        case let refreshed as TokenRefreshed:

            self = .validated(token: refreshed.token)

        default:
            break
        }
    }
}

struct ValidatedAuthenticationState : AuthenticationState {
    var token: String

    var description: String { return "The authentication token is \(token)." }

    mutating func react(to event: Event) {
        switch event {

        case let expiration as TokenExpired:
            print("Expiring token: \(token)")
            self = .expired(expiration.date)
        case _ as TokenRejected:
            self = .invalid
        case _ as UserLoggedOut:
            self = .invalid
        default:
            break
        }
    }
}
If AuthenticationState is not public, then compiler can make an optimization and turn existentials and indirect calls into an enum, essentially.
We can even split public protocols into open and public, as we did with classes, to allow for expressing this intent more explicitly.

I don't understand the lines in the struct version where you assign something to `self`. What is ".invalid", for example? I thought you'd removed the enum altogether.

But pattern matching on structs is impossible—we can change that with a separate proposal. For example, we can allow destructuring structs/enums/classes by any combination of their properties:

struct S {
    var foo: Int
    var bar: Double
    var buz: String { return "42" }
}

let s = S(foo: 42, bar: 42.0)

let S(foo: x, buz: z) = s

I was under the impression that we could switch over anything that had the `~=` operator defined. Is that not the case?

- Dave Sweeris

···

Sent from my iPhone

On Jan 11, 2017, at 14:57, Anton Zhilin via swift-evolution <swift-evolution@swift.org> wrote:

I'll summarize my thoughts for now, after thinking about it for a bit
longer:

Plusses:
+ In some cases it's nicer to have the ability to group functionality at
each case rather than spread across methods lower in the type definition.
I've written enums that were like state machines where being able to have
the next state transition and other associated data grouped with the case
would have made the code a bit cleaner, IMO.
+ This is the cleanest syntax I've seen proposed for this idea.

Things that worry me:
– It's a new way to express the same idea without necessarily *simplifying*
it in terms of code complexity.
– It adds a fair amount of complexity to the compiler, to build the
implicit self-switch and merge the implementations for each partial
property/function.
– It would be the first time, I believe, in Swift where properties/method
implementations are attached to *values* rather than just to the *type*.
That's quite a departure from the way we model these concepts.
– The use of the type-defined implementation as the default when a
case-defined implementation is omitted can lead to some bizarre
combinations, which I think need to be addressed in the proposal. Consider
this:

enum Foo {
  var description: String {
    switch self {
    case .bar: return "bar"
    case .baz: return "baz"
    default: return ""
  }

  case bar
  case baz
  case quux {
    var description: String { return "quux" }
  }
}

Should the user be able to omit the `default` case because everything is
exhaustively covered without it? *Conceptually* yes, but that would require
the compiler to analyze the switch statement inside `description` and
understand that, which is probably not feasible for anything but the
simplest examples (and perhaps not even for those). Otherwise, it would
presumably transform the implementation to this:

  var description: String {
    switch self {
    case .quux: return "quux"
    default:
      switch self {
      case .bar: return "bar"
      case .baz: return "baz"
      default: return ""
    }
  }

...which produces the expected output, but the generated code might not be
ideal.

That makes me question whether we should allow default implementations at
all. If we didn't allow it, we potentially require the user to write *more*
code because they would have to duplicate the defaults under each case
separately. But it opens a door to potential confusion if we do allow them.

So... I guess I'm on the fence right now. I want to support this from the
point of view that syntactically it would improve the quality of some of
the enums I've written, but I'm hedging a bit conservatively because of
those concerns above.

···

On Wed, Jan 11, 2017 at 7:08 AM David Sweeris <davesweeris@mac.com> wrote:

On Jan 11, 2017, at 08:48, Derrick Ho via swift-evolution < > swift-evolution@swift.org> wrote:

Interesting proposal Tim. So instead of repeating the enum cases multiple
times we repeat the functions multiple times?

I feel like this merely flipping the complexity but not really getting rid
of it.

I *think* you have correctly summarized the proposal.

The complexity can't be gotten rid of (except maybe with macros...
depending on the details, of course). Sometimes viewing the same thing from
a different angle can be helpful. As long as this syntax is "in addition
to" and not "instead of", the only downside I can see to allowing this is a
bit of "language bloat", which doesn't particularly bother me. I don't know
how hard it would be for the compiler to sort out which "mode" it should
parse the enum in, though, especially since you might want to write some
parts in the regular way and some parts in this new way.

So... I guess I'm +1, pending someone who knows more about compiler
internals weighing in on implementation implications.

- Dave Sweeris

I like the idea behind this, but I think the proposed syntax abuses
extension, which applies to types not individual cases. Putting the code
outside the enum also seems wrong, and it’s difficult to see how the
compiler would handle it, in particular ensuring exhaustiveness. So I have
come up with an alternate syntax using the example from the proposal that I
think works a bit better (or at least might inspire someone else to improve
it further):

enum TokenState {
    case expired(at: Date)
    case validated(token: String)

    caseprotocol { // (1)
        mutating func react(to event: Event)
        var description: String
    }
    caseimpl<.expired(let at)> { // (2)
        var description: String {
            return "Expired at \(at)"
        }
        mutating func react(to event: Event) {
            switch event {
            case _ as TokenRefreshed:
                self = .validated(token: event.token)
            default: break
            }
        }
    }
    caseimpl<.validated(let token)> { // (3)
        var description: String {
            return "Token \(token) has been validated."
        }
        mutating func react(to event: Event) {
            switch event {
            case _ as TokenRejected:
                self = .expired(at: Date())
            case _ as UserLoggedOut:
                self = .expired(at: Date())
            default: break
            }
        }
    }
    // (4)
}

I find this easier to parse manually, and I think the compiler would too…

   - At (1) it is equivalent to defining a normal protocol outside the
   enum. The enum itself would implicitly conform to this protocol. Calculated
   properties would be implicitly get-only in the protocol.
   - At (2) and (3) this would be equivalent to defining a static object
   that conforms to a compiler-modified version of the protocol, with static
   implementations of the getters/functions. Mutating functions would have an
   inout ref to self and they would all have any associated values sent in
   as per the let in the caseimpl declaration. The body would come directly
   from the programmer code.
   - At (4) the compiler would generate the enum’s implementation to
   conform to the protocol, this would switch on self and call the
   appropriate static functions. As per the original proposal.

It might be good if you don’t have to repeat all the func signatures in
each caseimpl - I considered a different syntax where you defined a casefunc
(similar to a typealias) like casefunc ReactTo = mutating func react(to
event: Event) then in the caseimpl you could just do ReactTo = { func body
here } but it didn’t seem to fit. Hopefully someone else will have some
ideas to reduce the repetition.

I don't understand the lines in the struct version where you assign
something to `self`. What is ".invalid", for example? I thought you'd
removed the enum altogether.

I totally overlooked it. That can't be done with protocols, scrap that
suggestion.

But pattern matching on structs is impossible—we can change that with a
separate proposal. For example, we can allow destructuring
structs/enums/classes by any combination of their properties:

struct S {
    var foo: Int
    var bar: Double
    var buz: String { return "42" }
}
let s = S(foo: 42, bar: 42.0)
let S(foo: x, buz: z) = s

I was under the impression that we could switch over anything that had the
`~=` operator defined. Is that not the case?

Well, this one is a bit different, but it's horribly off-topic for this
thread.

···

2017-01-12 0:51 GMT+03:00 David Sweeris <davesweeris@mac.com>:

I think you may be reacting to the very first draft. `extension` isn't used at all in the newest draft, and no code can exist outside the enum. In fact, there's a fair amount of similarity to your idea in the newest proposal.

···

On Jan 11, 2017, at 6:43 PM, Jay Abbott via swift-evolution <swift-evolution@swift.org> wrote:

I like the idea behind this, but I think the proposed syntax abuses extension, which applies to types not individual cases. Putting the code outside the enum also seems wrong, and it’s difficult to see how the compiler would handle it, in particular ensuring exhaustiveness. So I have come up with an alternate syntax using the example from the proposal that I think works a bit better (or at least might inspire someone else to improve it further):

enum TokenState {
    case expired(at: Date)
    case validated(token: String)

    caseprotocol { // (1)
        mutating func react(to event: Event)
        var description: String
    }
    caseimpl<.expired(let at)> { // (2)
        var description: String {
            return "Expired at \(at)"
        }
        mutating func react(to event: Event) {
            switch event {
            case _ as TokenRefreshed:
                self = .validated(token: event.token)
            default: break
            }
        }
    }
    caseimpl<.validated(let token)> { // (3)
        var description: String {
            return "Token \(token) has been validated."
        }
        mutating func react(to event: Event) {
            switch event {
            case _ as TokenRejected:
                self = .expired(at: Date())
            case _ as UserLoggedOut:
                self = .expired(at: Date())
            default: break
            }
        }
    }
    // (4)
}
I find this easier to parse manually, and I think the compiler would too…

At (1) it is equivalent to defining a normal protocol outside the enum. The enum itself would implicitly conform to this protocol. Calculated properties would be implicitly get-only in the protocol.
At (2) and (3) this would be equivalent to defining a static object that conforms to a compiler-modified version of the protocol, with static implementations of the getters/functions. Mutating functions would have an inout ref to self and they would all have any associated values sent in as per the let in the caseimpl declaration. The body would come directly from the programmer code.
At (4) the compiler would generate the enum’s implementation to conform to the protocol, this would switch on self and call the appropriate static functions. As per the original proposal.
It might be good if you don’t have to repeat all the func signatures in each caseimpl - I considered a different syntax where you defined a casefunc (similar to a typealias) like casefunc ReactTo = mutating func react(to event: Event) then in the caseimpl you could just do ReactTo = { func body here } but it didn’t seem to fit. Hopefully someone else will have some ideas to reduce the repetition.

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

Thanks for pointing this out Tim. I had actually read the whole thread but
over a few days between other things and must have missed the relevant
updates.

I think you may be reacting to the very first draft. `extension` isn't used

at all in the newest draft, and no code can exist outside the enum. In
fact, there's a fair amount of similarity to your idea in the newest
proposal.

Draft of Swift Evolution Proposal for Case Blocks · GitHub

Oh yes - this is much better! Lovely in fact. Although it does lose the
benefit of at-a-glance understanding of the cases present in the enum when
they are on one line each (when the code gets bigger than in the example
they will be quite a way apart).

Fantastic points, all. Let me reply to a few.

First, the odd situations you mentioned with the defaults were spot on. I had seen them, and thought a bit about it, but having you write it out in code made it obvious that they're pretty harmful. I've removed defaults entirely (see the updated Alternatives and Errors sections). This is actually a really good indicator of whether you should use this syntax or not. If you'll save a noticeable amount of space by using a default, then this isn't the syntax for your situation.

To make that more obvious, I've added a Mixed Use Example (Draft of Swift Evolution Proposal for Case Blocks · GitHub) showing how simple it is to use the existing syntax when you have a default and a small number of exceptions, alongside the case block syntax for things that are unique for each case. So, to summarize, I think that forcing the additional code for each case is a good thing so that this syntax remains the exception used when needed, instead of becoming the rule.

Now to your other points. The intent isn't to alter the amount of code required to accomplish the task (code *complexity*), but to simplify *maintenance and understanding* by allowing you to organize the code either by property or by case (according to the merits of the code and your judgement). Indeed, I try to highlight in the proposal that this usually _increases_ code verbosity slightly in favor of the readability gained by shifting the code layout.

Finally, to your point about attaching properties to values (also echoed in David's musings about tagged unions vs Swift enums), it is different, but I'm not sure of a cleaner way to group all the code related to a single case for complex enums. In another light, no other type in Swift makes such heavy use of `switch` statements as a mechanism to organize code, and so this starts to make enum code look more like the rest of the code you find in Swift projects, which could be a good thing for long-term readability.

···

On Jan 11, 2017, at 9:22 AM, Tony Allevato <tony.allevato@gmail.com> wrote:

I'll summarize my thoughts for now, after thinking about it for a bit longer:

Plusses:
+ In some cases it's nicer to have the ability to group functionality at each case rather than spread across methods lower in the type definition. I've written enums that were like state machines where being able to have the next state transition and other associated data grouped with the case would have made the code a bit cleaner, IMO.
+ This is the cleanest syntax I've seen proposed for this idea.

Things that worry me:
– It's a new way to express the same idea without necessarily *simplifying* it in terms of code complexity.
– It adds a fair amount of complexity to the compiler, to build the implicit self-switch and merge the implementations for each partial property/function.
– It would be the first time, I believe, in Swift where properties/method implementations are attached to *values* rather than just to the *type*. That's quite a departure from the way we model these concepts.
– The use of the type-defined implementation as the default when a case-defined implementation is omitted can lead to some bizarre combinations, which I think need to be addressed in the proposal. Consider this:

enum Foo {
  var description: String {
    switch self {
    case .bar: return "bar"
    case .baz: return "baz"
    default: return ""
  }
 
  case bar
  case baz
  case quux {
    var description: String { return "quux" }
  }
}

Should the user be able to omit the `default` case because everything is exhaustively covered without it? *Conceptually* yes, but that would require the compiler to analyze the switch statement inside `description` and understand that, which is probably not feasible for anything but the simplest examples (and perhaps not even for those). Otherwise, it would presumably transform the implementation to this:

  var description: String {
    switch self {
    case .quux: return "quux"
    default:
      switch self {
      case .bar: return "bar"
      case .baz: return "baz"
      default: return ""
    }
  }

...which produces the expected output, but the generated code might not be ideal.

That makes me question whether we should allow default implementations at all. If we didn't allow it, we potentially require the user to write *more* code because they would have to duplicate the defaults under each case separately. But it opens a door to potential confusion if we do allow them.

So... I guess I'm on the fence right now. I want to support this from the point of view that syntactically it would improve the quality of some of the enums I've written, but I'm hedging a bit conservatively because of those concerns above.

On Wed, Jan 11, 2017 at 7:08 AM David Sweeris <davesweeris@mac.com <mailto:davesweeris@mac.com>> wrote:

On Jan 11, 2017, at 08:48, Derrick Ho via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Interesting proposal Tim. So instead of repeating the enum cases multiple times we repeat the functions multiple times?

I feel like this merely flipping the complexity but not really getting rid of it.

I think you have correctly summarized the proposal.

The complexity can't be gotten rid of (except maybe with macros... depending on the details, of course). Sometimes viewing the same thing from a different angle can be helpful. As long as this syntax is "in addition to" and not "instead of", the only downside I can see to allowing this is a bit of "language bloat", which doesn't particularly bother me. I don't know how hard it would be for the compiler to sort out which "mode" it should parse the enum in, though, especially since you might want to write some parts in the regular way and some parts in this new way.

So... I guess I'm +1, pending someone who knows more about compiler internals weighing in on implementation implications.

- Dave Sweeris

I'm glad you like it! I agree, it does lose the at-a-glace view of all cases. That's probably something you could still get with code-folding in the IDE, but it's still something you give up for this syntax. I would hope the small cost actually helps people stick with the old syntax for small enums, and consider this for more verbose cases.

···

On Jan 12, 2017, at 11:15 AM, Jay Abbott <jay@abbott.me.uk> wrote:

Thanks for pointing this out Tim. I had actually read the whole thread but over a few days between other things and must have missed the relevant updates.

I think you may be reacting to the very first draft. `extension` isn't used at all in the newest draft, and no code can exist outside the enum. In fact, there's a fair amount of similarity to your idea in the newest proposal.

Draft of Swift Evolution Proposal for Case Blocks · GitHub

Oh yes - this is much better! Lovely in fact. Although it does lose the benefit of at-a-glance understanding of the cases present in the enum when they are on one line each (when the code gets bigger than in the example they will be quite a way apart).

+1

Site: http://balancingrock.nl
Blog: http://swiftrien.blogspot.com
Github: Swiftrien (Rien) · GitHub
Project: http://swiftfire.nl

···

On 13 Jan 2017, at 03:01, Tim Shadel via swift-evolution <swift-evolution@swift.org> wrote:

Fantastic points, all. Let me reply to a few.

First, the odd situations you mentioned with the defaults were spot on. I had seen them, and thought a bit about it, but having you write it out in code made it obvious that they're pretty harmful. I've removed defaults entirely (see the updated Alternatives and Errors sections). This is actually a really good indicator of whether you should use this syntax or not. If you'll save a noticeable amount of space by using a default, then this isn't the syntax for your situation.

To make that more obvious, I've added a Mixed Use Example (Draft of Swift Evolution Proposal for Case Blocks · GitHub) showing how simple it is to use the existing syntax when you have a default and a small number of exceptions, alongside the case block syntax for things that are unique for each case. So, to summarize, I think that forcing the additional code for each case is a good thing so that this syntax remains the exception used when needed, instead of becoming the rule.

Now to your other points. The intent isn't to alter the amount of code required to accomplish the task (code *complexity*), but to simplify *maintenance and understanding* by allowing you to organize the code either by property or by case (according to the merits of the code and your judgement). Indeed, I try to highlight in the proposal that this usually _increases_ code verbosity slightly in favor of the readability gained by shifting the code layout.

Finally, to your point about attaching properties to values (also echoed in David's musings about tagged unions vs Swift enums), it is different, but I'm not sure of a cleaner way to group all the code related to a single case for complex enums. In another light, no other type in Swift makes such heavy use of `switch` statements as a mechanism to organize code, and so this starts to make enum code look more like the rest of the code you find in Swift projects, which could be a good thing for long-term readability.

On Jan 11, 2017, at 9:22 AM, Tony Allevato <tony.allevato@gmail.com> wrote:

I'll summarize my thoughts for now, after thinking about it for a bit longer:

Plusses:
+ In some cases it's nicer to have the ability to group functionality at each case rather than spread across methods lower in the type definition. I've written enums that were like state machines where being able to have the next state transition and other associated data grouped with the case would have made the code a bit cleaner, IMO.
+ This is the cleanest syntax I've seen proposed for this idea.

Things that worry me:
– It's a new way to express the same idea without necessarily *simplifying* it in terms of code complexity.
– It adds a fair amount of complexity to the compiler, to build the implicit self-switch and merge the implementations for each partial property/function.
– It would be the first time, I believe, in Swift where properties/method implementations are attached to *values* rather than just to the *type*. That's quite a departure from the way we model these concepts.
– The use of the type-defined implementation as the default when a case-defined implementation is omitted can lead to some bizarre combinations, which I think need to be addressed in the proposal. Consider this:

enum Foo {
  var description: String {
    switch self {
    case .bar: return "bar"
    case .baz: return "baz"
    default: return ""
  }
 
  case bar
  case baz
  case quux {
    var description: String { return "quux" }
  }
}

Should the user be able to omit the `default` case because everything is exhaustively covered without it? *Conceptually* yes, but that would require the compiler to analyze the switch statement inside `description` and understand that, which is probably not feasible for anything but the simplest examples (and perhaps not even for those). Otherwise, it would presumably transform the implementation to this:

  var description: String {
    switch self {
    case .quux: return "quux"
    default:
      switch self {
      case .bar: return "bar"
      case .baz: return "baz"
      default: return ""
    }
  }

...which produces the expected output, but the generated code might not be ideal.

That makes me question whether we should allow default implementations at all. If we didn't allow it, we potentially require the user to write *more* code because they would have to duplicate the defaults under each case separately. But it opens a door to potential confusion if we do allow them.

So... I guess I'm on the fence right now. I want to support this from the point of view that syntactically it would improve the quality of some of the enums I've written, but I'm hedging a bit conservatively because of those concerns above.

On Wed, Jan 11, 2017 at 7:08 AM David Sweeris <davesweeris@mac.com> wrote:

On Jan 11, 2017, at 08:48, Derrick Ho via swift-evolution <swift-evolution@swift.org> wrote:

Interesting proposal Tim. So instead of repeating the enum cases multiple times we repeat the functions multiple times?

I feel like this merely flipping the complexity but not really getting rid of it.

I think you have correctly summarized the proposal.

The complexity can't be gotten rid of (except maybe with macros... depending on the details, of course). Sometimes viewing the same thing from a different angle can be helpful. As long as this syntax is "in addition to" and not "instead of", the only downside I can see to allowing this is a bit of "language bloat", which doesn't particularly bother me. I don't know how hard it would be for the compiler to sort out which "mode" it should parse the enum in, though, especially since you might want to write some parts in the regular way and some parts in this new way.

So... I guess I'm +1, pending someone who knows more about compiler internals weighing in on implementation implications.

- Dave Sweeris

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

With everything quiet on this discussion since my last update removing defaults, I've gone ahead and submitted a pull request (https://github.com/apple/swift-evolution/pull/585\) with the latest draft. I'm certainly happy to still update it if anything arises.

Thanks to all who helped out!

—Tim

···

On Jan 13, 2017, at 12:23 AM, Rien <Rien@Balancingrock.nl> wrote:

+1

Site: http://balancingrock.nl
Blog: http://swiftrien.blogspot.com
Github: Swiftrien (Rien) · GitHub
Project: http://swiftfire.nl

On 13 Jan 2017, at 03:01, Tim Shadel via swift-evolution <swift-evolution@swift.org> wrote:

Fantastic points, all. Let me reply to a few.

First, the odd situations you mentioned with the defaults were spot on. I had seen them, and thought a bit about it, but having you write it out in code made it obvious that they're pretty harmful. I've removed defaults entirely (see the updated Alternatives and Errors sections). This is actually a really good indicator of whether you should use this syntax or not. If you'll save a noticeable amount of space by using a default, then this isn't the syntax for your situation.

To make that more obvious, I've added a Mixed Use Example (Draft of Swift Evolution Proposal for Case Blocks · GitHub) showing how simple it is to use the existing syntax when you have a default and a small number of exceptions, alongside the case block syntax for things that are unique for each case. So, to summarize, I think that forcing the additional code for each case is a good thing so that this syntax remains the exception used when needed, instead of becoming the rule.

Now to your other points. The intent isn't to alter the amount of code required to accomplish the task (code *complexity*), but to simplify *maintenance and understanding* by allowing you to organize the code either by property or by case (according to the merits of the code and your judgement). Indeed, I try to highlight in the proposal that this usually _increases_ code verbosity slightly in favor of the readability gained by shifting the code layout.

Finally, to your point about attaching properties to values (also echoed in David's musings about tagged unions vs Swift enums), it is different, but I'm not sure of a cleaner way to group all the code related to a single case for complex enums. In another light, no other type in Swift makes such heavy use of `switch` statements as a mechanism to organize code, and so this starts to make enum code look more like the rest of the code you find in Swift projects, which could be a good thing for long-term readability.

On Jan 11, 2017, at 9:22 AM, Tony Allevato <tony.allevato@gmail.com> wrote:

I'll summarize my thoughts for now, after thinking about it for a bit longer:

Plusses:
+ In some cases it's nicer to have the ability to group functionality at each case rather than spread across methods lower in the type definition. I've written enums that were like state machines where being able to have the next state transition and other associated data grouped with the case would have made the code a bit cleaner, IMO.
+ This is the cleanest syntax I've seen proposed for this idea.

Things that worry me:
– It's a new way to express the same idea without necessarily *simplifying* it in terms of code complexity.
– It adds a fair amount of complexity to the compiler, to build the implicit self-switch and merge the implementations for each partial property/function.
– It would be the first time, I believe, in Swift where properties/method implementations are attached to *values* rather than just to the *type*. That's quite a departure from the way we model these concepts.
– The use of the type-defined implementation as the default when a case-defined implementation is omitted can lead to some bizarre combinations, which I think need to be addressed in the proposal. Consider this:

enum Foo {
 var description: String {
   switch self {
   case .bar: return "bar"
   case .baz: return "baz"
   default: return ""
 }

 case bar
 case baz
 case quux {
   var description: String { return "quux" }
 }
}

Should the user be able to omit the `default` case because everything is exhaustively covered without it? *Conceptually* yes, but that would require the compiler to analyze the switch statement inside `description` and understand that, which is probably not feasible for anything but the simplest examples (and perhaps not even for those). Otherwise, it would presumably transform the implementation to this:

 var description: String {
   switch self {
   case .quux: return "quux"
   default:
     switch self {
     case .bar: return "bar"
     case .baz: return "baz"
     default: return ""
   }
 }

...which produces the expected output, but the generated code might not be ideal.

That makes me question whether we should allow default implementations at all. If we didn't allow it, we potentially require the user to write *more* code because they would have to duplicate the defaults under each case separately. But it opens a door to potential confusion if we do allow them.

So... I guess I'm on the fence right now. I want to support this from the point of view that syntactically it would improve the quality of some of the enums I've written, but I'm hedging a bit conservatively because of those concerns above.

On Wed, Jan 11, 2017 at 7:08 AM David Sweeris <davesweeris@mac.com> wrote:

On Jan 11, 2017, at 08:48, Derrick Ho via swift-evolution <swift-evolution@swift.org> wrote:

Interesting proposal Tim. So instead of repeating the enum cases multiple times we repeat the functions multiple times?

I feel like this merely flipping the complexity but not really getting rid of it.

I think you have correctly summarized the proposal.

The complexity can't be gotten rid of (except maybe with macros... depending on the details, of course). Sometimes viewing the same thing from a different angle can be helpful. As long as this syntax is "in addition to" and not "instead of", the only downside I can see to allowing this is a bit of "language bloat", which doesn't particularly bother me. I don't know how hard it would be for the compiler to sort out which "mode" it should parse the enum in, though, especially since you might want to write some parts in the regular way and some parts in this new way.

So... I guess I'm +1, pending someone who knows more about compiler internals weighing in on implementation implications.

- Dave Sweeris

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