Proposal: Stored properties for enums


(Jonathan Hise Kaldma) #1

Hi everyone,

Being able to associate values with enums is one of the features I love about Swift, especially together with the ability to make enums contain themselves. It makes it really easy to model tree-like structures, e.g. arithmetic expressions:

enum Expression {
    case Number(Double)
    case Variable(String)
    indirect case Unary(Operator, Expression)
    indirect case Binary(Operator, Expression, Expression)
}

This works great with Swift’s switch statement and let binding, and cuts out a lot of unnecessary code. But it breaks down if you need to keep track of more data for each tree node, e.g. if you’re making a parser and need to keep track of source location. This could easily be solved with stored properties:

enum Expression {
    case Number(Double)
    case Variable(String)
    indirect case Unary(Operator, Expression)
    indirect case Binary(Operator, Expression, Expression)

    var location: Int = 0
    var length: Int = 0
}

If the properties have default values, you would still get the regular enum initializer without properties like today:

let expr = .Number(3)

But you would also get a memberwise initializer with the properties after the associated values, which you can use if you the properties don’t have default values or if you just want to set them to something else.

let expr = .Number(3, location: 5, length: 1)

Other than that, enums would work just like they do today. Aside from the previous example, I think this would simplify a lot of use cases where you have a struct and an accompanying enum, so rather than:

struct Something {
  var type: SomethingType
  var width: Int
  var height: Int
}

enum SomethingType {
  case TypeA
  case TypeB
}

You would just have:

enum Something {
  case TypeA
  case TypeB

  var width: Int
  var length: Int
}

Feels very Swifty to me.

Thanks,
Jonathan


Enum var field
(Chris Lattner) #2

The concern with doing this is that Swift currently distinguishes clearly between product & sum types, and this is a good thing. With your proposal, there would be no difference between:

struct X {
  var a, b : Int
}

and:

enum X {
  var a, b : Int
}

As such, there would be no reason to have concepts in the language. Some might argue that this is good (fewer things == better), but I’d argue that this is worse, because it can be better for clearly different things to be... different.

Further, Swift has a simple way to express this today use: an enum inside of a struct. As far as I can tell, the only bad thing about this is that it breaks pattern matching over the recursive case. However, this is a already an annoying aspect of swift’s current design that show up in other ways: for example, structs are generally more powerful than tuples, except that tuples can be pattern matched over.

IMO, the right way to fix this is to introduce the concept of pattern matching over structs and classes. If you dig through history, you’ll see that Joe Groff implemented a sketch of this functionality in the compiler, but it was never fully baked so it was turned off before Swift 1.0, and eventually got removed. A better baked out design and implementation could fix the problems you raise without causing a conflation between structs and enums.

-Chris

···

On Dec 9, 2015, at 12:28 PM, Jonathan Hise Kaldma via swift-evolution <swift-evolution@swift.org> wrote:

But it breaks down if you need to keep track of more data for each tree node, e.g. if you’re making a parser and need to keep track of source location. This could easily be solved with stored properties:

enum Expression {
   case Number(Double)
   case Variable(String)
   indirect case Unary(Operator, Expression)
   indirect case Binary(Operator, Expression, Expression)

   var location: Int = 0
   var length: Int = 0
}


(Matthew Johnson) #3

Chris,

Does this mean the compiler generated projection functions for labeled associated values that appear in every case are also out? This proposal looks to me like pretty straightforward syntactic sugar for associated values that appear in every case.

I understand if you want to avoid syntactic sugar for that in order to draw a stronger distinction between sum and product types but I don’t understand why it might be a good idea to provide compiler support for the projection functions but not sugar for streamlining the declaration of the associated values that should be present for every case.

Matthew

···

On Dec 9, 2015, at 7:01 PM, Chris Lattner via swift-evolution <swift-evolution@swift.org> wrote:

On Dec 9, 2015, at 12:28 PM, Jonathan Hise Kaldma via swift-evolution <swift-evolution@swift.org> wrote:

But it breaks down if you need to keep track of more data for each tree node, e.g. if you’re making a parser and need to keep track of source location. This could easily be solved with stored properties:

enum Expression {
  case Number(Double)
  case Variable(String)
  indirect case Unary(Operator, Expression)
  indirect case Binary(Operator, Expression, Expression)

  var location: Int = 0
  var length: Int = 0
}

The concern with doing this is that Swift currently distinguishes clearly between product & sum types, and this is a good thing. With your proposal, there would be no difference between:

struct X {
var a, b : Int
}

and:

enum X {
var a, b : Int
}

As such, there would be no reason to have concepts in the language. Some might argue that this is good (fewer things == better), but I’d argue that this is worse, because it can be better for clearly different things to be... different.

Further, Swift has a simple way to express this today use: an enum inside of a struct. As far as I can tell, the only bad thing about this is that it breaks pattern matching over the recursive case. However, this is a already an annoying aspect of swift’s current design that show up in other ways: for example, structs are generally more powerful than tuples, except that tuples can be pattern matched over.

IMO, the right way to fix this is to introduce the concept of pattern matching over structs and classes. If you dig through history, you’ll see that Joe Groff implemented a sketch of this functionality in the compiler, but it was never fully baked so it was turned off before Swift 1.0, and eventually got removed. A better baked out design and implementation could fix the problems you raise without causing a conflation between structs and enums.

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


(Tommy van der Vorst) #4

Hi Jonathan,

One of the great things about enums with attached values is that they define discrete 'states' in which the enum can be, and what values to expect in each state. If you want to add information to an enum value that is not related to its state, it sounds to me like you rather should use a struct for that (possibly embedding the 'state' part), like you say, i.e.:

enum Expression {
  case Number(Double)
  case Variable(String)
  indirect case Unary(Operator, Expression)
  indirect case Binary(Operator, Expression, Expression)
}

struct Capture {
  let location: Int
  let length: Int
  let expression: Expression

  init(_ expression: Expression, location: Int, length: Int) {...}
}

let expr = Capture(.Number(3.0), location: .., length: ...)

In my view, adding independent stored properties to enum would make this not much easier to write or read, but only more confusion for people expecting the 'state machine' like behaviour of enum.

Best,
Tommy.

···

Op 9 dec. 2015, om 21:28 heeft Jonathan Hise Kaldma via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> het volgende geschreven:

Hi everyone,

Being able to associate values with enums is one of the features I love about Swift, especially together with the ability to make enums contain themselves. It makes it really easy to model tree-like structures, e.g. arithmetic expressions:

enum Expression {
   case Number(Double)
   case Variable(String)
   indirect case Unary(Operator, Expression)
   indirect case Binary(Operator, Expression, Expression)
}

This works great with Swift’s switch statement and let binding, and cuts out a lot of unnecessary code. But it breaks down if you need to keep track of more data for each tree node, e.g. if you’re making a parser and need to keep track of source location. This could easily be solved with stored properties:

enum Expression {
   case Number(Double)
   case Variable(String)
   indirect case Unary(Operator, Expression)
   indirect case Binary(Operator, Expression, Expression)

   var location: Int = 0
   var length: Int = 0
}

If the properties have default values, you would still get the regular enum initializer without properties like today:

let expr = .Number(3)

But you would also get a memberwise initializer with the properties after the associated values, which you can use if you the properties don’t have default values or if you just want to set them to something else.

let expr = .Number(3, location: 5, length: 1)

Other than that, enums would work just like they do today. Aside from the previous example, I think this would simplify a lot of use cases where you have a struct and an accompanying enum, so rather than:

struct Something {
var type: SomethingType
var width: Int
var height: Int
}

enum SomethingType {
case TypeA
case TypeB
}

You would just have:

enum Something {
case TypeA
case TypeB

var width: Int
var length: Int
}

Feels very Swifty to me.

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


(Jonathan Hise Kaldma) #5

But it breaks down if you need to keep track of more data for each tree node, e.g. if you’re making a parser and need to keep track of source location. This could easily be solved with stored properties:

enum Expression {
  case Number(Double)
  case Variable(String)
  indirect case Unary(Operator, Expression)
  indirect case Binary(Operator, Expression, Expression)

  var location: Int = 0
  var length: Int = 0
}

The concern with doing this is that Swift currently distinguishes clearly between product & sum types, and this is a good thing. With your proposal, there would be no difference between:

struct X {
var a, b : Int
}

and:

enum X {
var a, b : Int
}

The difference would be that you can't initialize the enum, since it doesn't have any cases. Essentially a struct would be an enum with zero cases.

As such, there would be no reason to have concepts in the language. Some might argue that this is good (fewer things == better), but I’d argue that this is worse, because it can be better for clearly different things to be... different.

Struct would be the zero-case subset of enum. Related, but different.

Further, Swift has a simple way to express this today use: an enum inside of a struct. As far as I can tell, the only bad thing about this is that it breaks pattern matching over the recursive case. However, this is a already an annoying aspect of swift’s current design that show up in other ways: for example, structs are generally more powerful than tuples, except that tuples can be pattern matched over.

The problem with an enum inside a struct, is that, as Dave pointed out, it puts more focus on the things that are similar, and less on the things that are different, which is often the opposite of what you want.

Also, it usually doesn't really capture what you're trying to model.

struct Block {
  var type: BlockType
  var pos, length : Float
}

enum BlockType {
  case Audio(AudioData)
  case Video(VideoData)
}

Why is BlockType a different thing? A block can be audio or video, not something else. And modeling different cases of something, isn't that what enums are for? It would be much more elegant and readable to just say:

enum Block {
  case Audio(AudioData)
  case Video(VideoData)
  var pos, length : Float
}

IMO, the right way to fix this is to introduce the concept of pattern matching over structs and classes. If you dig through history, you’ll see that Joe Groff implemented a sketch of this functionality in the compiler, but it was never fully baked so it was turned off before Swift 1.0, and eventually got removed. A better baked out design and implementation could fix the problems you raise without causing a conflation between structs and enums.

- Chris

Thanks! I'll take a look.

And thanks for a great language!

Best,
Jonathan

···

10 dec. 2015 kl. 02:01 skrev Chris Lattner <clattner@apple.com>:

On Dec 9, 2015, at 12:28 PM, Jonathan Hise Kaldma via swift-evolution <swift-evolution@swift.org> wrote:


(Dave DeLong) #6

I came across the exact same problem a couple of months ago (expressions with source ranges).

The issue with the struct-and-enum approach is that it increases the amount of indirection you have in order to get at the data you care about. When dealing with expressions, you almost always care about the expression type (i.e., the enum value). Very rarely do you need the range of the original expression in the source string. However, the struct-and-enum approach optimizes for the accessing range, but not the type and its associated values.

I would be very much in favor of seeing stored properties on enums.

Dave

···

On Dec 9, 2015, at 1:36 PM, Tommy van der Vorst via swift-evolution <swift-evolution@swift.org> wrote:

Hi Jonathan,

One of the great things about enums with attached values is that they define discrete 'states' in which the enum can be, and what values to expect in each state. If you want to add information to an enum value that is not related to its state, it sounds to me like you rather should use a struct for that (possibly embedding the 'state' part), like you say, i.e.:

enum Expression {
  case Number(Double)
  case Variable(String)
  indirect case Unary(Operator, Expression)
  indirect case Binary(Operator, Expression, Expression)
}

struct Capture {
  let location: Int
  let length: Int
  let expression: Expression

  init(_ expression: Expression, location: Int, length: Int) {...}
}

let expr = Capture(.Number(3.0), location: .., length: ...)

In my view, adding independent stored properties to enum would make this not much easier to write or read, but only more confusion for people expecting the 'state machine' like behaviour of enum.

Best,
Tommy.

Op 9 dec. 2015, om 21:28 heeft Jonathan Hise Kaldma via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> het volgende geschreven:

Hi everyone,

Being able to associate values with enums is one of the features I love about Swift, especially together with the ability to make enums contain themselves. It makes it really easy to model tree-like structures, e.g. arithmetic expressions:

enum Expression {
   case Number(Double)
   case Variable(String)
   indirect case Unary(Operator, Expression)
   indirect case Binary(Operator, Expression, Expression)
}

This works great with Swift’s switch statement and let binding, and cuts out a lot of unnecessary code. But it breaks down if you need to keep track of more data for each tree node, e.g. if you’re making a parser and need to keep track of source location. This could easily be solved with stored properties:

enum Expression {
   case Number(Double)
   case Variable(String)
   indirect case Unary(Operator, Expression)
   indirect case Binary(Operator, Expression, Expression)

   var location: Int = 0
   var length: Int = 0
}

If the properties have default values, you would still get the regular enum initializer without properties like today:

let expr = .Number(3)

But you would also get a memberwise initializer with the properties after the associated values, which you can use if you the properties don’t have default values or if you just want to set them to something else.

let expr = .Number(3, location: 5, length: 1)

Other than that, enums would work just like they do today. Aside from the previous example, I think this would simplify a lot of use cases where you have a struct and an accompanying enum, so rather than:

struct Something {
var type: SomethingType
var width: Int
var height: Int
}

enum SomethingType {
case TypeA
case TypeB
}

You would just have:

enum Something {
case TypeA
case TypeB

var width: Int
var length: Int
}

Feels very Swifty to me.

Thanks,
Jonathan
_______________________________________________
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
https://lists.swift.org/mailman/listinfo/swift-evolution


(Jonathan Hise Kaldma) #7

None of the state-like nature of enums would be lost.

enum Something {
  case StateA
  case StateB(Double)
  var prop: Int
}

Would simply be a another way to write:

enum Something {
  case StateA(Int)
  case StateB(Double, Int)
}

The properties would just be like associated values that are included for every case. Of course when there's only two cases and one property, it's not a problem. But if you have more cases and more common values, it quickly gets out of hand.

I don't really see how the containing struct solves the problem in the example, since Expression only has other Expressions as children. You could of course modify it to contain Captures instead, but that doesn't express the recursiveness of expressions in the same elegant way.

Best,
Jonathan

···

9 dec. 2015 kl. 21:36 skrev Tommy van der Vorst <tommy@pixelspark.nl>:

Hi Jonathan,

One of the great things about enums with attached values is that they define discrete 'states' in which the enum can be, and what values to expect in each state. If you want to add information to an enum value that is not related to its state, it sounds to me like you rather should use a struct for that (possibly embedding the 'state' part), like you say, i.e.:

enum Expression {
  case Number(Double)
  case Variable(String)
  indirect case Unary(Operator, Expression)
  indirect case Binary(Operator, Expression, Expression)
}

struct Capture {
  let location: Int
  let length: Int
  let expression: Expression

  init(_ expression: Expression, location: Int, length: Int) {...}
}

let expr = Capture(.Number(3.0), location: .., length: ...)

In my view, adding independent stored properties to enum would make this not much easier to write or read, but only more confusion for people expecting the 'state machine' like behaviour of enum.

Best,
Tommy.

Op 9 dec. 2015, om 21:28 heeft Jonathan Hise Kaldma via swift-evolution <swift-evolution@swift.org> het volgende geschreven:

Hi everyone,

Being able to associate values with enums is one of the features I love about Swift, especially together with the ability to make enums contain themselves. It makes it really easy to model tree-like structures, e.g. arithmetic expressions:

enum Expression {
   case Number(Double)
   case Variable(String)
   indirect case Unary(Operator, Expression)
   indirect case Binary(Operator, Expression, Expression)
}

This works great with Swift’s switch statement and let binding, and cuts out a lot of unnecessary code. But it breaks down if you need to keep track of more data for each tree node, e.g. if you’re making a parser and need to keep track of source location. This could easily be solved with stored properties:

enum Expression {
   case Number(Double)
   case Variable(String)
   indirect case Unary(Operator, Expression)
   indirect case Binary(Operator, Expression, Expression)

   var location: Int = 0
   var length: Int = 0
}

If the properties have default values, you would still get the regular enum initializer without properties like today:

let expr = .Number(3)

But you would also get a memberwise initializer with the properties after the associated values, which you can use if you the properties don’t have default values or if you just want to set them to something else.

let expr = .Number(3, location: 5, length: 1)

Other than that, enums would work just like they do today. Aside from the previous example, I think this would simplify a lot of use cases where you have a struct and an accompanying enum, so rather than:

struct Something {
var type: SomethingType
var width: Int
var height: Int
}

enum SomethingType {
case TypeA
case TypeB
}

You would just have:

enum Something {
case TypeA
case TypeB

var width: Int
var length: Int
}

Feels very Swifty to me.

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


#8

A use case for stored properties in enums would be improvements to ErrorType.

Today a struct is a common practical solution for defining an ErrorType with shared properties between error cases. And this removes the ability to use the nice pattern matching in catch clauses.

Gwendal Roué

···

Le 9 déc. 2015 à 22:06, Dave DeLong via swift-evolution <swift-evolution@swift.org> a écrit :

I came across the exact same problem a couple of months ago (expressions with source ranges).

The issue with the struct-and-enum approach is that it increases the amount of indirection you have in order to get at the data you care about. When dealing with expressions, you almost always care about the expression type (i.e., the enum value). Very rarely do you need the range of the original expression in the source string. However, the struct-and-enum approach optimizes for the accessing range, but not the type and its associated values.

I would be very much in favor of seeing stored properties on enums.

Dave

On Dec 9, 2015, at 1:36 PM, Tommy van der Vorst via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Hi Jonathan,

One of the great things about enums with attached values is that they define discrete 'states' in which the enum can be, and what values to expect in each state. If you want to add information to an enum value that is not related to its state, it sounds to me like you rather should use a struct for that (possibly embedding the 'state' part), like you say, i.e.:

enum Expression {
  case Number(Double)
  case Variable(String)
  indirect case Unary(Operator, Expression)
  indirect case Binary(Operator, Expression, Expression)
}

struct Capture {
  let location: Int
  let length: Int
  let expression: Expression

  init(_ expression: Expression, location: Int, length: Int) {...}
}

let expr = Capture(.Number(3.0), location: .., length: ...)

In my view, adding independent stored properties to enum would make this not much easier to write or read, but only more confusion for people expecting the 'state machine' like behaviour of enum.

Best,
Tommy.

Op 9 dec. 2015, om 21:28 heeft Jonathan Hise Kaldma via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> het volgende geschreven:

Hi everyone,

Being able to associate values with enums is one of the features I love about Swift, especially together with the ability to make enums contain themselves. It makes it really easy to model tree-like structures, e.g. arithmetic expressions:

enum Expression {
   case Number(Double)
   case Variable(String)
   indirect case Unary(Operator, Expression)
   indirect case Binary(Operator, Expression, Expression)
}

This works great with Swift’s switch statement and let binding, and cuts out a lot of unnecessary code. But it breaks down if you need to keep track of more data for each tree node, e.g. if you’re making a parser and need to keep track of source location. This could easily be solved with stored properties:

enum Expression {
   case Number(Double)
   case Variable(String)
   indirect case Unary(Operator, Expression)
   indirect case Binary(Operator, Expression, Expression)

   var location: Int = 0
   var length: Int = 0
}

If the properties have default values, you would still get the regular enum initializer without properties like today:

let expr = .Number(3)

But you would also get a memberwise initializer with the properties after the associated values, which you can use if you the properties don’t have default values or if you just want to set them to something else.

let expr = .Number(3, location: 5, length: 1)

Other than that, enums would work just like they do today. Aside from the previous example, I think this would simplify a lot of use cases where you have a struct and an accompanying enum, so rather than:

struct Something {
var type: SomethingType
var width: Int
var height: Int
}

enum SomethingType {
case TypeA
case TypeB
}

You would just have:

enum Something {
case TypeA
case TypeB

var width: Int
var length: Int
}

Feels very Swifty to me.

Thanks,
Jonathan
_______________________________________________
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

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


(Al Skipp) #9

Would the following construction address your use case?

struct DataInfo<DataType> {
  var data: DataType
  var pos, length: Float
}

enum Block {
  case Audio(DataInfo<AudioData>)
  case Video(DataInfo<VideoData>)
}

···

On 10 Dec 2015, at 10:14, Jonathan Hise Kaldma via swift-evolution <swift-evolution@swift.org> wrote:

10 dec. 2015 kl. 02:01 skrev Chris Lattner <clattner@apple.com>:

On Dec 9, 2015, at 12:28 PM, Jonathan Hise Kaldma via swift-evolution <swift-evolution@swift.org> wrote:
But it breaks down if you need to keep track of more data for each tree node, e.g. if you’re making a parser and need to keep track of source location. This could easily be solved with stored properties:

enum Expression {
case Number(Double)
case Variable(String)
indirect case Unary(Operator, Expression)
indirect case Binary(Operator, Expression, Expression)

var location: Int = 0
var length: Int = 0
}

The concern with doing this is that Swift currently distinguishes clearly between product & sum types, and this is a good thing. With your proposal, there would be no difference between:

struct X {
var a, b : Int
}

and:

enum X {
var a, b : Int
}

The difference would be that you can't initialize the enum, since it doesn't have any cases. Essentially a struct would be an enum with zero cases.

As such, there would be no reason to have concepts in the language. Some might argue that this is good (fewer things == better), but I’d argue that this is worse, because it can be better for clearly different things to be... different.

Struct would be the zero-case subset of enum. Related, but different.

Further, Swift has a simple way to express this today use: an enum inside of a struct. As far as I can tell, the only bad thing about this is that it breaks pattern matching over the recursive case. However, this is a already an annoying aspect of swift’s current design that show up in other ways: for example, structs are generally more powerful than tuples, except that tuples can be pattern matched over.

The problem with an enum inside a struct, is that, as Dave pointed out, it puts more focus on the things that are similar, and less on the things that are different, which is often the opposite of what you want.

Also, it usually doesn't really capture what you're trying to model.

struct Block {
var type: BlockType
var pos, length : Float
}

enum BlockType {
case Audio(AudioData)
case Video(VideoData)
}

Why is BlockType a different thing? A block can be audio or video, not something else. And modeling different cases of something, isn't that what enums are for? It would be much more elegant and readable to just say:

enum Block {
case Audio(AudioData)
case Video(VideoData)
var pos, length : Float
}

IMO, the right way to fix this is to introduce the concept of pattern matching over structs and classes. If you dig through history, you’ll see that Joe Groff implemented a sketch of this functionality in the compiler, but it was never fully baked so it was turned off before Swift 1.0, and eventually got removed. A better baked out design and implementation could fix the problems you raise without causing a conflation between structs and enums.

- Chris

Thanks! I'll take a look.

And thanks for a great language!

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


(Jonathan Hise Kaldma) #10

Not really. It certainly works, but it's hard to follow. Also, this example was imagining some type of editor with blocks on a timeline. So the pos and length should really be properties of the block, not of the data.

Without enum properties, I agree with Chris: enum inside struct is the best way to model this type of thing.

My problem isn't really that you can't do these things today, it's that the solutions are ugly, hard to read, and 10x as long as they need to be. Expressions for example can easily be modeled with structs conforming to a protocol:

protocol Expression {
  var loc, len : Int
}

struct ValueExpression: Expression {
  var value: Double
  var loc, len : Int
}

struct UnaryExpression: Expression {
  var op: Operator
  var expr: Expression
  var loc, len : Int
}

struct BinaryExpression: Expression {
  var op: Operator
  var left, right : Expression
  var loc, len : Int
}

But that's a lot of boilerplate to express something that's actually pretty simple conceptually. With enum properties it is just:

enum Expression {
  case Value(Double)
  indirect case Unary(Operator, Expression)
  indirect case Binary(Operator, Expression, Expression)
  var pos, len : Int
}

Since readability is one of the main goals for Swift, I think it makes sense to include them.

Best,
Jonathan

···

10 dec. 2015 kl. 12:00 skrev Al Skipp <al_skipp@fastmail.fm>:

Would the following construction address your use case?

struct DataInfo<DataType> {
var data: DataType
var pos, length: Float
}

enum Block {
case Audio(DataInfo<AudioData>)
case Video(DataInfo<VideoData>)
}

On 10 Dec 2015, at 10:14, Jonathan Hise Kaldma via swift-evolution <swift-evolution@swift.org> wrote:

10 dec. 2015 kl. 02:01 skrev Chris Lattner <clattner@apple.com>:

On Dec 9, 2015, at 12:28 PM, Jonathan Hise Kaldma via swift-evolution <swift-evolution@swift.org> wrote:
But it breaks down if you need to keep track of more data for each tree node, e.g. if you’re making a parser and need to keep track of source location. This could easily be solved with stored properties:

enum Expression {
case Number(Double)
case Variable(String)
indirect case Unary(Operator, Expression)
indirect case Binary(Operator, Expression, Expression)

var location: Int = 0
var length: Int = 0
}

The concern with doing this is that Swift currently distinguishes clearly between product & sum types, and this is a good thing. With your proposal, there would be no difference between:

struct X {
var a, b : Int
}

and:

enum X {
var a, b : Int
}

The difference would be that you can't initialize the enum, since it doesn't have any cases. Essentially a struct would be an enum with zero cases.

As such, there would be no reason to have concepts in the language. Some might argue that this is good (fewer things == better), but I’d argue that this is worse, because it can be better for clearly different things to be... different.

Struct would be the zero-case subset of enum. Related, but different.

Further, Swift has a simple way to express this today use: an enum inside of a struct. As far as I can tell, the only bad thing about this is that it breaks pattern matching over the recursive case. However, this is a already an annoying aspect of swift’s current design that show up in other ways: for example, structs are generally more powerful than tuples, except that tuples can be pattern matched over.

The problem with an enum inside a struct, is that, as Dave pointed out, it puts more focus on the things that are similar, and less on the things that are different, which is often the opposite of what you want.

Also, it usually doesn't really capture what you're trying to model.

struct Block {
var type: BlockType
var pos, length : Float
}

enum BlockType {
case Audio(AudioData)
case Video(VideoData)
}

Why is BlockType a different thing? A block can be audio or video, not something else. And modeling different cases of something, isn't that what enums are for? It would be much more elegant and readable to just say:

enum Block {
case Audio(AudioData)
case Video(VideoData)
var pos, length : Float
}

IMO, the right way to fix this is to introduce the concept of pattern matching over structs and classes. If you dig through history, you’ll see that Joe Groff implemented a sketch of this functionality in the compiler, but it was never fully baked so it was turned off before Swift 1.0, and eventually got removed. A better baked out design and implementation could fix the problems you raise without causing a conflation between structs and enums.

- Chris

Thanks! I'll take a look.

And thanks for a great language!

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


(Frederick Kellison-Linn) #11

I’d be in favor of this as well, and have run into similar issues. Although one thing to consider is that if this change were made, would a struct not just become a special case of an enum, (that is, an enum with a single case)?

FKL

···

On Dec 9, 2015, at 4:14 PM, Gwendal Roué via swift-evolution <swift-evolution@swift.org> wrote:

A use case for stored properties in enums would be improvements to ErrorType.

Today a struct is a common practical solution for defining an ErrorType with shared properties between error cases. And this removes the ability to use the nice pattern matching in catch clauses.

Gwendal Roué

Le 9 déc. 2015 à 22:06, Dave DeLong via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> a écrit :

I came across the exact same problem a couple of months ago (expressions with source ranges).

The issue with the struct-and-enum approach is that it increases the amount of indirection you have in order to get at the data you care about. When dealing with expressions, you almost always care about the expression type (i.e., the enum value). Very rarely do you need the range of the original expression in the source string. However, the struct-and-enum approach optimizes for the accessing range, but not the type and its associated values.

I would be very much in favor of seeing stored properties on enums.

Dave

On Dec 9, 2015, at 1:36 PM, Tommy van der Vorst via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Hi Jonathan,

One of the great things about enums with attached values is that they define discrete 'states' in which the enum can be, and what values to expect in each state. If you want to add information to an enum value that is not related to its state, it sounds to me like you rather should use a struct for that (possibly embedding the 'state' part), like you say, i.e.:

enum Expression {
  case Number(Double)
  case Variable(String)
  indirect case Unary(Operator, Expression)
  indirect case Binary(Operator, Expression, Expression)
}

struct Capture {
  let location: Int
  let length: Int
  let expression: Expression

  init(_ expression: Expression, location: Int, length: Int) {...}
}

let expr = Capture(.Number(3.0), location: .., length: ...)

In my view, adding independent stored properties to enum would make this not much easier to write or read, but only more confusion for people expecting the 'state machine' like behaviour of enum.

Best,
Tommy.

Op 9 dec. 2015, om 21:28 heeft Jonathan Hise Kaldma via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> het volgende geschreven:

Hi everyone,

Being able to associate values with enums is one of the features I love about Swift, especially together with the ability to make enums contain themselves. It makes it really easy to model tree-like structures, e.g. arithmetic expressions:

enum Expression {
   case Number(Double)
   case Variable(String)
   indirect case Unary(Operator, Expression)
   indirect case Binary(Operator, Expression, Expression)
}

This works great with Swift’s switch statement and let binding, and cuts out a lot of unnecessary code. But it breaks down if you need to keep track of more data for each tree node, e.g. if you’re making a parser and need to keep track of source location. This could easily be solved with stored properties:

enum Expression {
   case Number(Double)
   case Variable(String)
   indirect case Unary(Operator, Expression)
   indirect case Binary(Operator, Expression, Expression)

   var location: Int = 0
   var length: Int = 0
}

If the properties have default values, you would still get the regular enum initializer without properties like today:

let expr = .Number(3)

But you would also get a memberwise initializer with the properties after the associated values, which you can use if you the properties don’t have default values or if you just want to set them to something else.

let expr = .Number(3, location: 5, length: 1)

Other than that, enums would work just like they do today. Aside from the previous example, I think this would simplify a lot of use cases where you have a struct and an accompanying enum, so rather than:

struct Something {
var type: SomethingType
var width: Int
var height: Int
}

enum SomethingType {
case TypeA
case TypeB
}

You would just have:

enum Something {
case TypeA
case TypeB

var width: Int
var length: Int
}

Feels very Swifty to me.

Thanks,
Jonathan
_______________________________________________
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

_______________________________________________
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


(Tommy van der Vorst) #12

None of the state-like nature of enums would be lost.

enum Something {
case StateA
case StateB(Double)
var prop: Int
}

Would simply be a another way to write:

enum Something {
case StateA(Int)
case StateB(Double, Int)
}

Sure, but do we really need special syntax then? To me the above way of writing is much clearer on which data is available at what point than the variant with the separate 'var' declaration. You can even label the different values in the associated data tuple.

Putting data shared across states in a separate 'var' declaration introduces some other issues as well: when an enum is reassigned (i.e. self = .StateB(...)), is the variable emptied somehow, or is it kept? How would you even initialize the value of a non-optional stored property that is not part of the case associated tuple (as the variables are not a 'requirement' of the case tuple, perhaps only optionals should be allowed)?

/T


(Al Skipp) #13

Fair enough : )

I’d definitely be keen to maintain the distinction between product types and sum types and this proposal seems to lose that. If pattern matching on product types were introduced, it should solve this particular conundrum. Here’s how it could potentially look in Haskell using pattern matching in the ‘doStuff’ function. Not sure how the pattern matching would look in Swift, but this gives an impression of what’s possible.

data BlockType = Audio AudioData | Video VideoData

data Block = Block { dataType :: BlockType, pos :: Double, len :: Double }

doStuff :: Block -> IO ()
doStuff Block {dataType = (Audio audioData), pos = p, len = l} = … function implementation using audioData, p and l
doStuff Block {dataType = (Video videoData), pos = p, len = l} = … function implementation using videoData, p and l

···

On 10 Dec 2015, at 12:34, Jonathan Hise Kaldma <info@hisekaldma.com> wrote:

Not really. It certainly works, but it's hard to follow. Also, this example was imagining some type of editor with blocks on a timeline. So the pos and length should really be properties of the block, not of the data.


(Tommy van der Vorst) #14

Hi Jonathan,

I recently wrote a formula parser in Swift that also needs to keep the location+length for parsed expressions for syntax highlighting purposes. In my implementation I do not store the location+length in the expression tree itself, but maintain a separate map of [Expression: (location: Int, length: Int)] where Expression could be a Hashable enum type.

I was more or less forced to do it this way because the expression logic is in a different framework that doesn't care about syntax highlighting, but I think it also nicely separates concerns between expression logic and view logic. Perhaps this solution also works for you.

Best,
Tommy.

···

Op 10 dec. 2015, om 13:34 heeft Jonathan Hise Kaldma via swift-evolution <swift-evolution@swift.org> het volgende geschreven:

Not really. It certainly works, but it's hard to follow. Also, this example was imagining some type of editor with blocks on a timeline. So the pos and length should really be properties of the block, not of the data.

Without enum properties, I agree with Chris: enum inside struct is the best way to model this type of thing.

My problem isn't really that you can't do these things today, it's that the solutions are ugly, hard to read, and 10x as long as they need to be. Expressions for example can easily be modeled with structs conforming to a protocol:

protocol Expression {
var loc, len : Int
}

struct ValueExpression: Expression {
var value: Double
var loc, len : Int
}

struct UnaryExpression: Expression {
var op: Operator
var expr: Expression
var loc, len : Int
}

struct BinaryExpression: Expression {
var op: Operator
var left, right : Expression
var loc, len : Int
}

But that's a lot of boilerplate to express something that's actually pretty simple conceptually. With enum properties it is just:

enum Expression {
case Value(Double)
indirect case Unary(Operator, Expression)
indirect case Binary(Operator, Expression, Expression)
var pos, len : Int
}

Since readability is one of the main goals for Swift, I think it makes sense to include them.

Best,
Jonathan

10 dec. 2015 kl. 12:00 skrev Al Skipp <al_skipp@fastmail.fm>:

Would the following construction address your use case?

struct DataInfo<DataType> {
var data: DataType
var pos, length: Float
}

enum Block {
case Audio(DataInfo<AudioData>)
case Video(DataInfo<VideoData>)
}

On 10 Dec 2015, at 10:14, Jonathan Hise Kaldma via swift-evolution <swift-evolution@swift.org> wrote:

10 dec. 2015 kl. 02:01 skrev Chris Lattner <clattner@apple.com>:

On Dec 9, 2015, at 12:28 PM, Jonathan Hise Kaldma via swift-evolution <swift-evolution@swift.org> wrote:
But it breaks down if you need to keep track of more data for each tree node, e.g. if you’re making a parser and need to keep track of source location. This could easily be solved with stored properties:

enum Expression {
case Number(Double)
case Variable(String)
indirect case Unary(Operator, Expression)
indirect case Binary(Operator, Expression, Expression)

var location: Int = 0
var length: Int = 0
}

The concern with doing this is that Swift currently distinguishes clearly between product & sum types, and this is a good thing. With your proposal, there would be no difference between:

struct X {
var a, b : Int
}

and:

enum X {
var a, b : Int
}

The difference would be that you can't initialize the enum, since it doesn't have any cases. Essentially a struct would be an enum with zero cases.

As such, there would be no reason to have concepts in the language. Some might argue that this is good (fewer things == better), but I’d argue that this is worse, because it can be better for clearly different things to be... different.

Struct would be the zero-case subset of enum. Related, but different.

Further, Swift has a simple way to express this today use: an enum inside of a struct. As far as I can tell, the only bad thing about this is that it breaks pattern matching over the recursive case. However, this is a already an annoying aspect of swift’s current design that show up in other ways: for example, structs are generally more powerful than tuples, except that tuples can be pattern matched over.

The problem with an enum inside a struct, is that, as Dave pointed out, it puts more focus on the things that are similar, and less on the things that are different, which is often the opposite of what you want.

Also, it usually doesn't really capture what you're trying to model.

struct Block {
var type: BlockType
var pos, length : Float
}

enum BlockType {
case Audio(AudioData)
case Video(VideoData)
}

Why is BlockType a different thing? A block can be audio or video, not something else. And modeling different cases of something, isn't that what enums are for? It would be much more elegant and readable to just say:

enum Block {
case Audio(AudioData)
case Video(VideoData)
var pos, length : Float
}

IMO, the right way to fix this is to introduce the concept of pattern matching over structs and classes. If you dig through history, you’ll see that Joe Groff implemented a sketch of this functionality in the compiler, but it was never fully baked so it was turned off before Swift 1.0, and eventually got removed. A better baked out design and implementation could fix the problems you raise without causing a conflation between structs and enums.

- Chris

Thanks! I'll take a look.

And thanks for a great language!

Best,
Jonathan
_______________________________________________
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


(Alex Lew) #15

Interesting proposal! In Swift's docs on Pattern Matching (
https://github.com/apple/swift/blob/master/docs/Pattern%20Matching.rst),
the inability to easily access a value that is relevant to *all *cases of
an enum is explicitly mentioned as a minus of Swift's approach:

   - minus: needs boilerplate to project out a common member across
   multiple/all alternatives

The docs even mention that it might be worth providing "special
dispensations for ... projecting out common members."

This seems like an elegant solution to the all-alternatives problem (though
not the multiple-alternatives problem). +1

It is worth considering Frederick's point above that this essentially makes
struct X { ... } the same as enum X { case OnlyCase; ... }. Also, you'd
probably want to make sure that none of the associated values shared a name
with a stored property, to avoid confusion when initializing
(constructing?) new enums.

(Tommy, did you see the line in the original proposal suggesting that you'd
initialize stored properties as if they were additional named associated
values? E.g.: let expr = .Number(3, location: 5, length: 1). I think if you
were to reassign expr -- if it were a var -- you'd need to re-assign
location and length, too; they wouldn't transfer.)

-Alex

···

On Wed, Dec 9, 2015 at 4:44 PM, Tommy van der Vorst via swift-evolution < swift-evolution@swift.org> wrote:

> None of the state-like nature of enums would be lost.
>
> enum Something {
> case StateA
> case StateB(Double)
> var prop: Int
> }
>
> Would simply be a another way to write:
>
> enum Something {
> case StateA(Int)
> case StateB(Double, Int)
> }

Sure, but do we really need special syntax then? To me the above way of
writing is much clearer on which data is available at what point than the
variant with the separate 'var' declaration. You can even label the
different values in the associated data tuple.

Putting data shared across states in a separate 'var' declaration
introduces some other issues as well: when an enum is reassigned (i.e. self
= .StateB(...)), is the variable emptied somehow, or is it kept? How would
you even initialize the value of a non-optional stored property that is not
part of the case associated tuple (as the variables are not a 'requirement'
of the case tuple, perhaps only optionals should be allowed)?

/T

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


(Tommy van der Vorst) #16

(Tommy, did you see the line in the original proposal suggesting that you'd initialize stored properties as if they were additional named associated values? E.g.: let expr = .Number(3, location: 5, length: 1). I think if you were to reassign expr -- if it were a var -- you'd need to re-assign location and length, too; they wouldn't transfer.)

Nope, I missed that (sorry). If at all, this seems a reasonable way to implement it.

By the way it requires there be no duplicate field labels. Also it seems logical to either have a fixed initializer parameter order (first the associated values, then the labeled common ones in order of declaration), *or* to allow any order (in case all associated values have labels as well).

My only objection to this proposal is still whether the number of cases where this is useful warrants the added complexity and potential confusion for newcomers. I haven't encountered many situations where these stored common properties would be very useful but ymmv.

/T

···

Op 10 dec. 2015, om 00:06 heeft Alex Lew <alexl.mail+swift@gmail.com> het volgende geschreven:

Interesting proposal! In Swift's docs on Pattern Matching (https://github.com/apple/swift/blob/master/docs/Pattern%20Matching.rst), the inability to easily access a value that is relevant to all cases of an enum is explicitly mentioned as a minus of Swift's approach:
minus: needs boilerplate to project out a common member across multiple/all alternatives
The docs even mention that it might be worth providing "special dispensations for ... projecting out common members."

This seems like an elegant solution to the all-alternatives problem (though not the multiple-alternatives problem). +1

It is worth considering Frederick's point above that this essentially makes struct X { ... } the same as enum X { case OnlyCase; ... }. Also, you'd probably want to make sure that none of the associated values shared a name with a stored property, to avoid confusion when initializing (constructing?) new enums.

(Tommy, did you see the line in the original proposal suggesting that you'd initialize stored properties as if they were additional named associated values? E.g.: let expr = .Number(3, location: 5, length: 1). I think if you were to reassign expr -- if it were a var -- you'd need to re-assign location and length, too; they wouldn't transfer.)

-Alex

On Wed, Dec 9, 2015 at 4:44 PM, Tommy van der Vorst via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
> None of the state-like nature of enums would be lost.
>
> enum Something {
> case StateA
> case StateB(Double)
> var prop: Int
> }
>
> Would simply be a another way to write:
>
> enum Something {
> case StateA(Int)
> case StateB(Double, Int)
> }

Sure, but do we really need special syntax then? To me the above way of writing is much clearer on which data is available at what point than the variant with the separate 'var' declaration. You can even label the different values in the associated data tuple.

Putting data shared across states in a separate 'var' declaration introduces some other issues as well: when an enum is reassigned (i.e. self = .StateB(...)), is the variable emptied somehow, or is it kept? How would you even initialize the value of a non-optional stored property that is not part of the case associated tuple (as the variables are not a 'requirement' of the case tuple, perhaps only optionals should be allowed)?

/T

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


(Jonathan Hise Kaldma) #17

If it's important to keep sum and product types separate, perhaps this could be a new type entirely. A "composite" perhaps?

Best,
Jonathan

···

10 dec. 2015 kl. 14:53 skrev Al Skipp <al_skipp@fastmail.fm>:

On 10 Dec 2015, at 12:34, Jonathan Hise Kaldma <info@hisekaldma.com> wrote:

Not really. It certainly works, but it's hard to follow. Also, this example was imagining some type of editor with blocks on a timeline. So the pos and length should really be properties of the block, not of the data.

Fair enough : )

I’d definitely be keen to maintain the distinction between product types and sum types and this proposal seems to lose that. If pattern matching on product types were introduced, it should solve this particular conundrum. Here’s how it could potentially look in Haskell using pattern matching in the ‘doStuff’ function. Not sure how the pattern matching would look in Swift, but this gives an impression of what’s possible.

data BlockType = Audio AudioData | Video VideoData

data Block = Block { dataType :: BlockType, pos :: Double, len :: Double }

doStuff :: Block -> IO ()
doStuff Block {dataType = (Audio audioData), pos = p, len = l} = … function implementation using audioData, p and l
doStuff Block {dataType = (Video videoData), pos = p, len = l} = … function implementation using videoData, p and l


(Ilias Karim) #18

Great suggestion, Jonathan. I’ve encountered the desire for this feature a few times while implementing state machines for my iOS app.

How would switch statements pattern match enum stored properties?

Ilias

···

On Dec 9, 2015, at 3:34 PM, Tommy van der Vorst via swift-evolution <swift-evolution@swift.org> wrote:

(Tommy, did you see the line in the original proposal suggesting that you'd initialize stored properties as if they were additional named associated values? E.g.: let expr = .Number(3, location: 5, length: 1). I think if you were to reassign expr -- if it were a var -- you'd need to re-assign location and length, too; they wouldn't transfer.)

Nope, I missed that (sorry). If at all, this seems a reasonable way to implement it.

By the way it requires there be no duplicate field labels. Also it seems logical to either have a fixed initializer parameter order (first the associated values, then the labeled common ones in order of declaration), *or* to allow any order (in case all associated values have labels as well).

My only objection to this proposal is still whether the number of cases where this is useful warrants the added complexity and potential confusion for newcomers. I haven't encountered many situations where these stored common properties would be very useful but ymmv.

/T

Op 10 dec. 2015, om 00:06 heeft Alex Lew <alexl.mail+swift@gmail.com <mailto:alexl.mail+swift@gmail.com>> het volgende geschreven:

Interesting proposal! In Swift's docs on Pattern Matching (https://github.com/apple/swift/blob/master/docs/Pattern%20Matching.rst), the inability to easily access a value that is relevant to all cases of an enum is explicitly mentioned as a minus of Swift's approach:
minus: needs boilerplate to project out a common member across multiple/all alternatives
The docs even mention that it might be worth providing "special dispensations for ... projecting out common members."

This seems like an elegant solution to the all-alternatives problem (though not the multiple-alternatives problem). +1

It is worth considering Frederick's point above that this essentially makes struct X { ... } the same as enum X { case OnlyCase; ... }. Also, you'd probably want to make sure that none of the associated values shared a name with a stored property, to avoid confusion when initializing (constructing?) new enums.

(Tommy, did you see the line in the original proposal suggesting that you'd initialize stored properties as if they were additional named associated values? E.g.: let expr = .Number(3, location: 5, length: 1). I think if you were to reassign expr -- if it were a var -- you'd need to re-assign location and length, too; they wouldn't transfer.)

-Alex

On Wed, Dec 9, 2015 at 4:44 PM, Tommy van der Vorst via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
> None of the state-like nature of enums would be lost.
>
> enum Something {
> case StateA
> case StateB(Double)
> var prop: Int
> }
>
> Would simply be a another way to write:
>
> enum Something {
> case StateA(Int)
> case StateB(Double, Int)
> }

Sure, but do we really need special syntax then? To me the above way of writing is much clearer on which data is available at what point than the variant with the separate 'var' declaration. You can even label the different values in the associated data tuple.

Putting data shared across states in a separate 'var' declaration introduces some other issues as well: when an enum is reassigned (i.e. self = .StateB(...)), is the variable emptied somehow, or is it kept? How would you even initialize the value of a non-optional stored property that is not part of the case associated tuple (as the variables are not a 'requirement' of the case tuple, perhaps only optionals should be allowed)?

/T

_______________________________________________
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


(Frederick Kellison-Linn) #19

You wouldn’t need to pattern match against stored properties given that they are shared between all cases. E.g.

enum Expression {
   case Number(Double)
   case Variable(String)
   indirect case Unary(Operator, Expression)
   indirect case Binary(Operator, Expression, Expression)

   var location: Int = 0
   var length: Int = 0
}

could be used as such:

let expr = getSomeExpr()
switch expr {
case Number(let val):
    print("Number [\(val)] found at location \(expr.location).")
default:
    print(“Something else found at location \(expr.location).”)
}

FKL

···

On Dec 9, 2015, at 6:59 PM, Ilias Karim via swift-evolution <swift-evolution@swift.org> wrote:

Great suggestion, Jonathan. I’ve encountered the desire for this feature a few times while implementing state machines for my iOS app.

How would switch statements pattern match enum stored properties?

Ilias

On Dec 9, 2015, at 3:34 PM, Tommy van der Vorst via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

(Tommy, did you see the line in the original proposal suggesting that you'd initialize stored properties as if they were additional named associated values? E.g.: let expr = .Number(3, location: 5, length: 1). I think if you were to reassign expr -- if it were a var -- you'd need to re-assign location and length, too; they wouldn't transfer.)

Nope, I missed that (sorry). If at all, this seems a reasonable way to implement it.

By the way it requires there be no duplicate field labels. Also it seems logical to either have a fixed initializer parameter order (first the associated values, then the labeled common ones in order of declaration), *or* to allow any order (in case all associated values have labels as well).

My only objection to this proposal is still whether the number of cases where this is useful warrants the added complexity and potential confusion for newcomers. I haven't encountered many situations where these stored common properties would be very useful but ymmv.

/T

Op 10 dec. 2015, om 00:06 heeft Alex Lew <alexl.mail+swift@gmail.com <mailto:alexl.mail+swift@gmail.com>> het volgende geschreven:

Interesting proposal! In Swift's docs on Pattern Matching (https://github.com/apple/swift/blob/master/docs/Pattern%20Matching.rst), the inability to easily access a value that is relevant to all cases of an enum is explicitly mentioned as a minus of Swift's approach:
minus: needs boilerplate to project out a common member across multiple/all alternatives
The docs even mention that it might be worth providing "special dispensations for ... projecting out common members."

This seems like an elegant solution to the all-alternatives problem (though not the multiple-alternatives problem). +1

It is worth considering Frederick's point above that this essentially makes struct X { ... } the same as enum X { case OnlyCase; ... }. Also, you'd probably want to make sure that none of the associated values shared a name with a stored property, to avoid confusion when initializing (constructing?) new enums.

(Tommy, did you see the line in the original proposal suggesting that you'd initialize stored properties as if they were additional named associated values? E.g.: let expr = .Number(3, location: 5, length: 1). I think if you were to reassign expr -- if it were a var -- you'd need to re-assign location and length, too; they wouldn't transfer.)

-Alex

On Wed, Dec 9, 2015 at 4:44 PM, Tommy van der Vorst via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
> None of the state-like nature of enums would be lost.
>
> enum Something {
> case StateA
> case StateB(Double)
> var prop: Int
> }
>
> Would simply be a another way to write:
>
> enum Something {
> case StateA(Int)
> case StateB(Double, Int)
> }

Sure, but do we really need special syntax then? To me the above way of writing is much clearer on which data is available at what point than the variant with the separate 'var' declaration. You can even label the different values in the associated data tuple.

Putting data shared across states in a separate 'var' declaration introduces some other issues as well: when an enum is reassigned (i.e. self = .StateB(...)), is the variable emptied somehow, or is it kept? How would you even initialize the value of a non-optional stored property that is not part of the case associated tuple (as the variables are not a 'requirement' of the case tuple, perhaps only optionals should be allowed)?

/T

_______________________________________________
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

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


(Jonathan Hise Kaldma) #20

(Tommy, did you see the line in the original proposal suggesting that you'd initialize stored properties as if they were additional named associated values? E.g.: let expr = .Number(3, location: 5, length: 1). I think if you were to reassign expr -- if it were a var -- you'd need to re-assign location and length, too; they wouldn't transfer.)

Nope, I missed that (sorry). If at all, this seems a reasonable way to implement it.

Yup, reassigning an enum is really creating a new enum, so the properties wouldn't transfer.

By the way it requires there be no duplicate field labels. Also it seems logical to either have a fixed initializer parameter order (first the associated values, then the labeled common ones in order of declaration), *or* to allow any order (in case all associated values have labels as well).

Associated values today have to be initialized in the order they're defined (whether they have names or not), and I don't think that needs changing.

I think the associated values should still come first, since they're more closely related to the case and they're the values you're more likely to be interested in. The properties are the "extra stuff".

My only objection to this proposal is still whether the number of cases where this is useful warrants the added complexity and potential confusion for newcomers. I haven't encountered many situations where these stored common properties would be very useful but ymmv.

This might be one of those tools that looks really strange if you don't need it, but if you do need it, it's the exact right tool for the job.

Best,
Jonathan