[proposal draft] new syntax to access a given case's payload


(Jerome Duquennoy) #1

Summary
The aim of this proposal is to offer a new syntax to ease some uses of enums with payload.

Situation to improve:
Enums makes it possible to have explicate typing where it was not possible before. A classic example of that is filling a dictionary with data coming from a file or a stream (json, plist, …) : the types of possible values is finite : arrays, dicts, int, double, bool or string for json for exemple.
An enum can represent this finite range of possible types, its make the code and the API more self-documented.
Then, you have two possibilities to deal with this enum:
- using switch statements
- using the if case syntax introduced by swift 2

The drawback is that those two solutions can lead to writing code with high visual complexity, even though the logic behind is pretty simple.

Consider this example of a data dictionary, that a web service could have returned:
- book
  - title:
  - author:
    - name: Apple
    - age: 40

We can decode this in a variable of type [String:Value], where Value is:

enum Value {
  case integer(value: Int)
  case string(value: String)
  case dict(value: [String:Value])
  case null
}

Here is a snippet of code to access the age of the author:

if case .dict(let book) = data {
  if case .dict(let author) = book["author"] ?? .null {
    if case .integer(let age) = author["age"] ?? .null {
      // now we have the age
    }
  }
}

The multiple indentation levels can rapidly make this code unattractive to read, and we need to add a null case to the enum to deal with optional values.

Proposed solution:
I suggest to add a new syntax, using the case keyword to ease access to the payload of such enums :

let payloadContent = case? .enumCase(variable)

The payloadContent variable will be an optional, that can be either nil, or contain the payload of enumCase.
If the payload contains multiple variables, payloadContent will be a tupple.
This syntax can accommodate an optional variable as an input. If the value of variable is nil, then payloadContent will be nil.
Only enum cases with a payload can be used with this syntax (it would make no sens for cases without a payload).

With that syntax, the null case of the enum can be removed, and the code to access the age becomes:

let book = case? .dict(inputData)
let author = case? .dict(book?["author"])
let age = case? .integer(author?["age"])

Advantages:
- It leverages the well established notion of optional, and similar logic already exists in the language (for the as? operator notably).
- It does not add a new keyword
- It promotes the use of enum to enforce explicit typing, which leads to more self-documenting code
- It reduces the complexity of the code in situations such as the one of the exemple

Drawbacks:
- It adds a third use of the case keyword.
- In the proposed syntax, the variable between parenthesis is not the payload, but the variable to decode. This might be disturbing, as it differs from the other syntax of enum values.
- If the payload is an optional, it is not possible to differentiate a non-matching case and a matching case a nil payload.

Alternatives:
- Another syntax without parenthesis could be used to avoid the second drawback:
let payload = case? .enumCase variable

Impact on existing code:
None, this is adding a new syntax

This proposal would have no impact on the ABI, so it probably does not fit the stage 1 of swift 4’s roadmap. But I would be glad to have your feedback, so that I can have a proposal ready once we enter stage 2.
So what your thoughts on that proposal ?

Thanks

Jerome


(Rien) #2

While I don’t have time to get into detail right now, you may be interested in an approach I used for a JSON framework: http://github.com/swiftrien/swifterjson
In that approach I used the unix pipe operator “|” to chain a series of JSON accesses. In your example that would be written as:

guard let age = (json|”book”|”author”|”age”)?.integerValue else {…}

Maybe this could be of significance to your proposal?

Regards,
Rien.

···

On 26 Sep 2016, at 17:51, Jérôme Duquennoy via swift-evolution <swift-evolution@swift.org> wrote:

Summary
The aim of this proposal is to offer a new syntax to ease some uses of enums with payload.

Situation to improve:
Enums makes it possible to have explicate typing where it was not possible before. A classic example of that is filling a dictionary with data coming from a file or a stream (json, plist, …) : the types of possible values is finite : arrays, dicts, int, double, bool or string for json for exemple.
An enum can represent this finite range of possible types, its make the code and the API more self-documented.
Then, you have two possibilities to deal with this enum:
- using switch statements
- using the if case syntax introduced by swift 2

The drawback is that those two solutions can lead to writing code with high visual complexity, even though the logic behind is pretty simple.

Consider this example of a data dictionary, that a web service could have returned:
- book
  - title:
  - author:
    - name: Apple
    - age: 40

We can decode this in a variable of type [String:Value], where Value is:

enum Value {
  case integer(value: Int)
  case string(value: String)
  case dict(value: [String:Value])
  case null
}

Here is a snippet of code to access the age of the author:

if case .dict(let book) = data {
  if case .dict(let author) = book["author"] ?? .null {
    if case .integer(let age) = author["age"] ?? .null {
      // now we have the age
    }
  }
}

The multiple indentation levels can rapidly make this code unattractive to read, and we need to add a null case to the enum to deal with optional values.

Proposed solution:
I suggest to add a new syntax, using the case keyword to ease access to the payload of such enums :

let payloadContent = case? .enumCase(variable)

The payloadContent variable will be an optional, that can be either nil, or contain the payload of enumCase.
If the payload contains multiple variables, payloadContent will be a tupple.
This syntax can accommodate an optional variable as an input. If the value of variable is nil, then payloadContent will be nil.
Only enum cases with a payload can be used with this syntax (it would make no sens for cases without a payload).

With that syntax, the null case of the enum can be removed, and the code to access the age becomes:

let book = case? .dict(inputData)
let author = case? .dict(book?["author"])
let age = case? .integer(author?["age"])

Advantages:
- It leverages the well established notion of optional, and similar logic already exists in the language (for the as? operator notably).
- It does not add a new keyword
- It promotes the use of enum to enforce explicit typing, which leads to more self-documenting code
- It reduces the complexity of the code in situations such as the one of the exemple

Drawbacks:
- It adds a third use of the case keyword.
- In the proposed syntax, the variable between parenthesis is not the payload, but the variable to decode. This might be disturbing, as it differs from the other syntax of enum values.
- If the payload is an optional, it is not possible to differentiate a non-matching case and a matching case a nil payload.

Alternatives:
- Another syntax without parenthesis could be used to avoid the second drawback:
let payload = case? .enumCase variable

Impact on existing code:
None, this is adding a new syntax

This proposal would have no impact on the ABI, so it probably does not fit the stage 1 of swift 4’s roadmap. But I would be glad to have your feedback, so that I can have a proposal ready once we enter stage 2.
So what your thoughts on that proposal ?

Thanks

Jerome

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


(Robert Widmann) #3

Being a power user of this feature, I don’t think the existing syntax is cumbersome enough to warrant this kind of shortcut. I really like being able to

guard case let .dict(book) = data else {
  // bail out
}
guard case let .dict(author) = book["author"] ?? .null else {
  // bail out
}
guard case let .integer(age) = author["age"] ?? .null else {
  // bail out
}

// now we have the age

or more succinctly

guard case let .dict(book) = data,
      case let .dict(author) = book["author"] ?? .null,
      case let .integer(age) = author["age"] ?? .null
else {
  // bail out
}
// now we have the age

Because it forces me to think about the bailout case(s) and is really not that much longer than your proposed syntax

guard let book = case? .dict(inputData),
      let author = case? .dict(book?["author”]),
      let age = case? .integer(author?["age”])
else {
  // bail out
}
// now we have the age

~Robert Widmann

···

On Sep 26, 2016, at 11:51 AM, Jérôme Duquennoy via swift-evolution <swift-evolution@swift.org> wrote:

Summary
The aim of this proposal is to offer a new syntax to ease some uses of enums with payload.

Situation to improve:
Enums makes it possible to have explicate typing where it was not possible before. A classic example of that is filling a dictionary with data coming from a file or a stream (json, plist, …) : the types of possible values is finite : arrays, dicts, int, double, bool or string for json for exemple.
An enum can represent this finite range of possible types, its make the code and the API more self-documented.
Then, you have two possibilities to deal with this enum:
- using switch statements
- using the if case syntax introduced by swift 2

The drawback is that those two solutions can lead to writing code with high visual complexity, even though the logic behind is pretty simple.

Consider this example of a data dictionary, that a web service could have returned:
- book
  - title:
  - author:
    - name: Apple
    - age: 40

We can decode this in a variable of type [String:Value], where Value is:

enum Value {
  case integer(value: Int)
  case string(value: String)
  case dict(value: [String:Value])
  case null
}

Here is a snippet of code to access the age of the author:

if case .dict(let book) = data {
  if case .dict(let author) = book["author"] ?? .null {
    if case .integer(let age) = author["age"] ?? .null {
      // now we have the age
    }
  }
}

The multiple indentation levels can rapidly make this code unattractive to read, and we need to add a null case to the enum to deal with optional values.

Proposed solution:
I suggest to add a new syntax, using the case keyword to ease access to the payload of such enums :

let payloadContent = case? .enumCase(variable)

The payloadContent variable will be an optional, that can be either nil, or contain the payload of enumCase.
If the payload contains multiple variables, payloadContent will be a tupple.
This syntax can accommodate an optional variable as an input. If the value of variable is nil, then payloadContent will be nil.
Only enum cases with a payload can be used with this syntax (it would make no sens for cases without a payload).

With that syntax, the null case of the enum can be removed, and the code to access the age becomes:

let book = case? .dict(inputData)
let author = case? .dict(book?["author"])
let age = case? .integer(author?["age"])

Advantages:
- It leverages the well established notion of optional, and similar logic already exists in the language (for the as? operator notably).
- It does not add a new keyword
- It promotes the use of enum to enforce explicit typing, which leads to more self-documenting code
- It reduces the complexity of the code in situations such as the one of the exemple

Drawbacks:
- It adds a third use of the case keyword.
- In the proposed syntax, the variable between parenthesis is not the payload, but the variable to decode. This might be disturbing, as it differs from the other syntax of enum values.
- If the payload is an optional, it is not possible to differentiate a non-matching case and a matching case a nil payload.

Alternatives:
- Another syntax without parenthesis could be used to avoid the second drawback:
let payload = case? .enumCase variable

Impact on existing code:
None, this is adding a new syntax

This proposal would have no impact on the ABI, so it probably does not fit the stage 1 of swift 4’s roadmap. But I would be glad to have your feedback, so that I can have a proposal ready once we enter stage 2.
So what your thoughts on that proposal ?

Thanks

Jerome

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


(Joe Groff) #4

I think it's reasonable to want an expression for extracting the payload of a enum case as an Optional. Instead of introducing a new operator, though, we could say that the cases themselves behave as Optional properties of an enum, which would allow you to say:

if let book = inputData.dict,
   let author = book["author"].dict,
   let age = author["age"].integer { ... }

-Joe

···

On Sep 26, 2016, at 8:51 AM, Jérôme Duquennoy via swift-evolution <swift-evolution@swift.org> wrote:

Summary
The aim of this proposal is to offer a new syntax to ease some uses of enums with payload.

Situation to improve:
Enums makes it possible to have explicate typing where it was not possible before. A classic example of that is filling a dictionary with data coming from a file or a stream (json, plist, …) : the types of possible values is finite : arrays, dicts, int, double, bool or string for json for exemple.
An enum can represent this finite range of possible types, its make the code and the API more self-documented.
Then, you have two possibilities to deal with this enum:
- using switch statements
- using the if case syntax introduced by swift 2

The drawback is that those two solutions can lead to writing code with high visual complexity, even though the logic behind is pretty simple.

Consider this example of a data dictionary, that a web service could have returned:
- book
  - title:
  - author:
    - name: Apple
    - age: 40

We can decode this in a variable of type [String:Value], where Value is:

enum Value {
  case integer(value: Int)
  case string(value: String)
  case dict(value: [String:Value])
  case null
}

Here is a snippet of code to access the age of the author:

if case .dict(let book) = data {
  if case .dict(let author) = book["author"] ?? .null {
    if case .integer(let age) = author["age"] ?? .null {
      // now we have the age
    }
  }
}

The multiple indentation levels can rapidly make this code unattractive to read, and we need to add a null case to the enum to deal with optional values.

Proposed solution:
I suggest to add a new syntax, using the case keyword to ease access to the payload of such enums :

let payloadContent = case? .enumCase(variable)

The payloadContent variable will be an optional, that can be either nil, or contain the payload of enumCase.
If the payload contains multiple variables, payloadContent will be a tupple.
This syntax can accommodate an optional variable as an input. If the value of variable is nil, then payloadContent will be nil.
Only enum cases with a payload can be used with this syntax (it would make no sens for cases without a payload).

With that syntax, the null case of the enum can be removed, and the code to access the age becomes:

let book = case? .dict(inputData)
let author = case? .dict(book?["author"])
let age = case? .integer(author?["age"])

Advantages:
- It leverages the well established notion of optional, and similar logic already exists in the language (for the as? operator notably).
- It does not add a new keyword
- It promotes the use of enum to enforce explicit typing, which leads to more self-documenting code
- It reduces the complexity of the code in situations such as the one of the exemple

Drawbacks:
- It adds a third use of the case keyword.
- In the proposed syntax, the variable between parenthesis is not the payload, but the variable to decode. This might be disturbing, as it differs from the other syntax of enum values.
- If the payload is an optional, it is not possible to differentiate a non-matching case and a matching case a nil payload.

Alternatives:
- Another syntax without parenthesis could be used to avoid the second drawback:
let payload = case? .enumCase variable

Impact on existing code:
None, this is adding a new syntax

This proposal would have no impact on the ABI, so it probably does not fit the stage 1 of swift 4’s roadmap. But I would be glad to have your feedback, so that I can have a proposal ready once we enter stage 2.
So what your thoughts on that proposal ?


(Chris Lattner) #5

Hi Jérôme,

This proposal is out of scope for Swift 4 Stage 1, as it is a purely syntactic sugar proposal. We’re holding off discussion of those because we need to stay focused on more fundamental issues (e.g. the many things that affect ABI stability, like the incomplete generic system).

-Chris

···

On Sep 26, 2016, at 8:51 AM, Jérôme Duquennoy via swift-evolution <swift-evolution@swift.org> wrote:

Summary
The aim of this proposal is to offer a new syntax to ease some uses of enums with payload.


(Jerome Duquennoy) #6

This pattern matching syntax is very nice, for sure.
I think the proposed evolution would be a nice complement to it in two ways:
- being able to handle the case of optional using case? would avoid having to declare a case (.null in the exemple) that is purely technical, and does not match what the enum describes ;
- being able to assign a constant or variable outside of a flow control structure like “if" or “guard" can be useful (if you need to use the variable in multiple distinct places for exemple, be it in the same method, in different methods, or even in an instance variable) ;

But your point emphasise the second drawback I listed : in all those existing syntaxes, the parenthesis after the enum case contains the variable in which the payload will be stored.
The syntax I am suggesting does not follow the same logic, which is not nice.

That syntax would be closer:

let case? .enumCase(payloadItem1, payloadItem2) = input

It is pretty similar to the syntax used for pattern matching in guard, for and if.
The let and case keywords are swapped, the case gains a ‘?’ to reflect the fact that it will produce an optional, but the rest is the usual pattern matching syntax.

This solution would be even closer:

case? let .enumCase(payloadItem1, payloadItem2) = input

It mostly boils down to removing the guard keyword and its associated else.
But I feel very weird not having the let or var at the beginning of the line. Is that only me being too used to that, or does that matter for you too ?

Jerome

···

On 27 Sep 2016, at 01:04, Robert Widmann <devteam.codafi@gmail.com> wrote:

Being a power user of this feature, I don’t think the existing syntax is cumbersome enough to warrant this kind of shortcut. I really like being able to

guard case let .dict(book) = data else {
  // bail out
}
guard case let .dict(author) = book["author"] ?? .null else {
  // bail out
}
guard case let .integer(age) = author["age"] ?? .null else {
  // bail out
}

// now we have the age

or more succinctly

guard case let .dict(book) = data,
      case let .dict(author) = book["author"] ?? .null,
      case let .integer(age) = author["age"] ?? .null
else {
  // bail out
}
// now we have the age

Because it forces me to think about the bailout case(s) and is really not that much longer than your proposed syntax

guard let book = case? .dict(inputData),
      let author = case? .dict(book?["author”]),
      let age = case? .integer(author?["age”])
else {
  // bail out
}
// now we have the age

~Robert Widmann

On Sep 26, 2016, at 11:51 AM, Jérôme Duquennoy via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Summary
The aim of this proposal is to offer a new syntax to ease some uses of enums with payload.

Situation to improve:
Enums makes it possible to have explicate typing where it was not possible before. A classic example of that is filling a dictionary with data coming from a file or a stream (json, plist, …) : the types of possible values is finite : arrays, dicts, int, double, bool or string for json for exemple.
An enum can represent this finite range of possible types, its make the code and the API more self-documented.
Then, you have two possibilities to deal with this enum:
- using switch statements
- using the if case syntax introduced by swift 2

The drawback is that those two solutions can lead to writing code with high visual complexity, even though the logic behind is pretty simple.

Consider this example of a data dictionary, that a web service could have returned:
- book
  - title:
  - author:
    - name: Apple
    - age: 40

We can decode this in a variable of type [String:Value], where Value is:

enum Value {
  case integer(value: Int)
  case string(value: String)
  case dict(value: [String:Value])
  case null
}

Here is a snippet of code to access the age of the author:

if case .dict(let book) = data {
  if case .dict(let author) = book["author"] ?? .null {
    if case .integer(let age) = author["age"] ?? .null {
      // now we have the age
    }
  }
}

The multiple indentation levels can rapidly make this code unattractive to read, and we need to add a null case to the enum to deal with optional values.

Proposed solution:
I suggest to add a new syntax, using the case keyword to ease access to the payload of such enums :

let payloadContent = case? .enumCase(variable)

The payloadContent variable will be an optional, that can be either nil, or contain the payload of enumCase.
If the payload contains multiple variables, payloadContent will be a tupple.
This syntax can accommodate an optional variable as an input. If the value of variable is nil, then payloadContent will be nil.
Only enum cases with a payload can be used with this syntax (it would make no sens for cases without a payload).

With that syntax, the null case of the enum can be removed, and the code to access the age becomes:

let book = case? .dict(inputData)
let author = case? .dict(book?["author"])
let age = case? .integer(author?["age"])

Advantages:
- It leverages the well established notion of optional, and similar logic already exists in the language (for the as? operator notably).
- It does not add a new keyword
- It promotes the use of enum to enforce explicit typing, which leads to more self-documenting code
- It reduces the complexity of the code in situations such as the one of the exemple

Drawbacks:
- It adds a third use of the case keyword.
- In the proposed syntax, the variable between parenthesis is not the payload, but the variable to decode. This might be disturbing, as it differs from the other syntax of enum values.
- If the payload is an optional, it is not possible to differentiate a non-matching case and a matching case a nil payload.

Alternatives:
- Another syntax without parenthesis could be used to avoid the second drawback:
let payload = case? .enumCase variable

Impact on existing code:
None, this is adding a new syntax

This proposal would have no impact on the ABI, so it probably does not fit the stage 1 of swift 4’s roadmap. But I would be glad to have your feedback, so that I can have a proposal ready once we enter stage 2.
So what your thoughts on that proposal ?

Thanks

Jerome

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


(Thorsten Seitz) #7

Being a power user of this feature, I don’t think the existing syntax is cumbersome enough to warrant this kind of shortcut. I really like being able to

guard case let .dict(book) = data else {
  // bail out
}
guard case let .dict(author) = book["author"] ?? .null else {
  // bail out
}
guard case let .integer(age) = author["age"] ?? .null else {
  // bail out
}

// now we have the age

or more succinctly

guard case let .dict(book) = data,
      case let .dict(author) = book["author"] ?? .null,
      case let .integer(age) = author["age"] ?? .null
else {
  // bail out
}
// now we have the age

This is a very nice and readable solution. IMHO much better than the proposed syntax.

-Thorsten

···

Am 27.09.2016 um 01:04 schrieb Robert Widmann via swift-evolution <swift-evolution@swift.org>:

Because it forces me to think about the bailout case(s) and is really not that much longer than your proposed syntax

guard let book = case? .dict(inputData),
      let author = case? .dict(book?["author”]),
      let age = case? .integer(author?["age”])
else {
  // bail out
}
// now we have the age

~Robert Widmann

On Sep 26, 2016, at 11:51 AM, Jérôme Duquennoy via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Summary
The aim of this proposal is to offer a new syntax to ease some uses of enums with payload.

Situation to improve:
Enums makes it possible to have explicate typing where it was not possible before. A classic example of that is filling a dictionary with data coming from a file or a stream (json, plist, …) : the types of possible values is finite : arrays, dicts, int, double, bool or string for json for exemple.
An enum can represent this finite range of possible types, its make the code and the API more self-documented.
Then, you have two possibilities to deal with this enum:
- using switch statements
- using the if case syntax introduced by swift 2

The drawback is that those two solutions can lead to writing code with high visual complexity, even though the logic behind is pretty simple.

Consider this example of a data dictionary, that a web service could have returned:
- book
  - title:
  - author:
    - name: Apple
    - age: 40

We can decode this in a variable of type [String:Value], where Value is:

enum Value {
  case integer(value: Int)
  case string(value: String)
  case dict(value: [String:Value])
  case null
}

Here is a snippet of code to access the age of the author:

if case .dict(let book) = data {
  if case .dict(let author) = book["author"] ?? .null {
    if case .integer(let age) = author["age"] ?? .null {
      // now we have the age
    }
  }
}

The multiple indentation levels can rapidly make this code unattractive to read, and we need to add a null case to the enum to deal with optional values.

Proposed solution:
I suggest to add a new syntax, using the case keyword to ease access to the payload of such enums :

let payloadContent = case? .enumCase(variable)

The payloadContent variable will be an optional, that can be either nil, or contain the payload of enumCase.
If the payload contains multiple variables, payloadContent will be a tupple.
This syntax can accommodate an optional variable as an input. If the value of variable is nil, then payloadContent will be nil.
Only enum cases with a payload can be used with this syntax (it would make no sens for cases without a payload).

With that syntax, the null case of the enum can be removed, and the code to access the age becomes:

let book = case? .dict(inputData)
let author = case? .dict(book?["author"])
let age = case? .integer(author?["age"])

Advantages:
- It leverages the well established notion of optional, and similar logic already exists in the language (for the as? operator notably).
- It does not add a new keyword
- It promotes the use of enum to enforce explicit typing, which leads to more self-documenting code
- It reduces the complexity of the code in situations such as the one of the exemple

Drawbacks:
- It adds a third use of the case keyword.
- In the proposed syntax, the variable between parenthesis is not the payload, but the variable to decode. This might be disturbing, as it differs from the other syntax of enum values.
- If the payload is an optional, it is not possible to differentiate a non-matching case and a matching case a nil payload.

Alternatives:
- Another syntax without parenthesis could be used to avoid the second drawback:
let payload = case? .enumCase variable

Impact on existing code:
None, this is adding a new syntax

This proposal would have no impact on the ABI, so it probably does not fit the stage 1 of swift 4’s roadmap. But I would be glad to have your feedback, so that I can have a proposal ready once we enter stage 2.
So what your thoughts on that proposal ?

Thanks

Jerome

_______________________________________________
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


(Karl) #8

I was thinking the same thing. I’m finding myself writing a lot of convenience accessors of the type:

enum MyEnum<T> {
    case stateOne
    case stateTwo(Array<T>)
    case stateThree(T)
    case stateFour(Error, T)

    var error : (Error, T)? {
        if case .error(let r) = self { return r }
        return .none
    }
}

It would be nice if the compiler could generate these style of accessors, with the payload available as an optional tuple. Perhaps it would be called `var stateFourData : (Error, T)?` or something predictable.

While I’m on the subject, sometimes I want every case of an enum to be its own type, which is a subtype of the enum’s type - e.g. MyEnum<Int>.stateThree.self. That would allow you to keep a collection of MyEnum values which are guaranteed to all be the same case.

···

On 27 Sep 2016, at 18:55, Joe Groff via swift-evolution <swift-evolution@swift.org> wrote:

On Sep 26, 2016, at 8:51 AM, Jérôme Duquennoy via swift-evolution <swift-evolution@swift.org> wrote:

Summary
The aim of this proposal is to offer a new syntax to ease some uses of enums with payload.

Situation to improve:
Enums makes it possible to have explicate typing where it was not possible before. A classic example of that is filling a dictionary with data coming from a file or a stream (json, plist, …) : the types of possible values is finite : arrays, dicts, int, double, bool or string for json for exemple.
An enum can represent this finite range of possible types, its make the code and the API more self-documented.
Then, you have two possibilities to deal with this enum:
- using switch statements
- using the if case syntax introduced by swift 2

The drawback is that those two solutions can lead to writing code with high visual complexity, even though the logic behind is pretty simple.

Consider this example of a data dictionary, that a web service could have returned:
- book
- title:
- author:
   - name: Apple
   - age: 40

We can decode this in a variable of type [String:Value], where Value is:

enum Value {
case integer(value: Int)
case string(value: String)
case dict(value: [String:Value])
case null
}

Here is a snippet of code to access the age of the author:

if case .dict(let book) = data {
if case .dict(let author) = book["author"] ?? .null {
   if case .integer(let age) = author["age"] ?? .null {
     // now we have the age
   }
}
}

The multiple indentation levels can rapidly make this code unattractive to read, and we need to add a null case to the enum to deal with optional values.

Proposed solution:
I suggest to add a new syntax, using the case keyword to ease access to the payload of such enums :

let payloadContent = case? .enumCase(variable)

The payloadContent variable will be an optional, that can be either nil, or contain the payload of enumCase.
If the payload contains multiple variables, payloadContent will be a tupple.
This syntax can accommodate an optional variable as an input. If the value of variable is nil, then payloadContent will be nil.
Only enum cases with a payload can be used with this syntax (it would make no sens for cases without a payload).

With that syntax, the null case of the enum can be removed, and the code to access the age becomes:

let book = case? .dict(inputData)
let author = case? .dict(book?["author"])
let age = case? .integer(author?["age"])

Advantages:
- It leverages the well established notion of optional, and similar logic already exists in the language (for the as? operator notably).
- It does not add a new keyword
- It promotes the use of enum to enforce explicit typing, which leads to more self-documenting code
- It reduces the complexity of the code in situations such as the one of the exemple

Drawbacks:
- It adds a third use of the case keyword.
- In the proposed syntax, the variable between parenthesis is not the payload, but the variable to decode. This might be disturbing, as it differs from the other syntax of enum values.
- If the payload is an optional, it is not possible to differentiate a non-matching case and a matching case a nil payload.

Alternatives:
- Another syntax without parenthesis could be used to avoid the second drawback:
let payload = case? .enumCase variable

Impact on existing code:
None, this is adding a new syntax

This proposal would have no impact on the ABI, so it probably does not fit the stage 1 of swift 4’s roadmap. But I would be glad to have your feedback, so that I can have a proposal ready once we enter stage 2.
So what your thoughts on that proposal ?

I think it's reasonable to want an expression for extracting the payload of a enum case as an Optional. Instead of introducing a new operator, though, we could say that the cases themselves behave as Optional properties of an enum, which would allow you to say:

if let book = inputData.dict,
  let author = book["author"].dict,
  let age = author["age"].integer { ... }

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


(Robert Widmann) #9

I can solve the last one! You often want phantom types in Swift, and they can be had albeit a bit unsafely with a "sealed conformance" to a common protocol and a set of no-case (for phantom types) or single-case (for singleton types) enums. This this you can do terrible things like create type-level Nats https://gist.github.com/CodaFi/7bb3bd00f04a9b26fd71 and things indexed by them like Fin or even recover light dependent pattern matching with type(of:).

~Robert Widmann

2016/09/27 18:59、Karl via swift-evolution <swift-evolution@swift.org> のメッセージ:

···

On 27 Sep 2016, at 18:55, Joe Groff via swift-evolution <swift-evolution@swift.org> wrote:

On Sep 26, 2016, at 8:51 AM, Jérôme Duquennoy via swift-evolution <swift-evolution@swift.org> wrote:

Summary
The aim of this proposal is to offer a new syntax to ease some uses of enums with payload.

Situation to improve:
Enums makes it possible to have explicate typing where it was not possible before. A classic example of that is filling a dictionary with data coming from a file or a stream (json, plist, …) : the types of possible values is finite : arrays, dicts, int, double, bool or string for json for exemple.
An enum can represent this finite range of possible types, its make the code and the API more self-documented.
Then, you have two possibilities to deal with this enum:
- using switch statements
- using the if case syntax introduced by swift 2

The drawback is that those two solutions can lead to writing code with high visual complexity, even though the logic behind is pretty simple.

Consider this example of a data dictionary, that a web service could have returned:
- book
- title:
- author:
  - name: Apple
  - age: 40

We can decode this in a variable of type [String:Value], where Value is:

enum Value {
case integer(value: Int)
case string(value: String)
case dict(value: [String:Value])
case null
}

Here is a snippet of code to access the age of the author:

if case .dict(let book) = data {
if case .dict(let author) = book["author"] ?? .null {
  if case .integer(let age) = author["age"] ?? .null {
    // now we have the age
  }
}
}

The multiple indentation levels can rapidly make this code unattractive to read, and we need to add a null case to the enum to deal with optional values.

Proposed solution:
I suggest to add a new syntax, using the case keyword to ease access to the payload of such enums :

let payloadContent = case? .enumCase(variable)

The payloadContent variable will be an optional, that can be either nil, or contain the payload of enumCase.
If the payload contains multiple variables, payloadContent will be a tupple.
This syntax can accommodate an optional variable as an input. If the value of variable is nil, then payloadContent will be nil.
Only enum cases with a payload can be used with this syntax (it would make no sens for cases without a payload).

With that syntax, the null case of the enum can be removed, and the code to access the age becomes:

let book = case? .dict(inputData)
let author = case? .dict(book?["author"])
let age = case? .integer(author?["age"])

Advantages:
- It leverages the well established notion of optional, and similar logic already exists in the language (for the as? operator notably).
- It does not add a new keyword
- It promotes the use of enum to enforce explicit typing, which leads to more self-documenting code
- It reduces the complexity of the code in situations such as the one of the exemple

Drawbacks:
- It adds a third use of the case keyword.
- In the proposed syntax, the variable between parenthesis is not the payload, but the variable to decode. This might be disturbing, as it differs from the other syntax of enum values.
- If the payload is an optional, it is not possible to differentiate a non-matching case and a matching case a nil payload.

Alternatives:
- Another syntax without parenthesis could be used to avoid the second drawback:
let payload = case? .enumCase variable

Impact on existing code:
None, this is adding a new syntax

This proposal would have no impact on the ABI, so it probably does not fit the stage 1 of swift 4’s roadmap. But I would be glad to have your feedback, so that I can have a proposal ready once we enter stage 2.
So what your thoughts on that proposal ?

I think it's reasonable to want an expression for extracting the payload of a enum case as an Optional. Instead of introducing a new operator, though, we could say that the cases themselves behave as Optional properties of an enum, which would allow you to say:

if let book = inputData.dict,
let author = book["author"].dict,
let age = author["age"].integer { ... }

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

I was thinking the same thing. I’m finding myself writing a lot of convenience accessors of the type:

enum MyEnum<T> {
   case stateOne
   case stateTwo(Array<T>)
   case stateThree(T)
   case stateFour(Error, T)

   var error : (Error, T)? {
       if case .error(let r) = self { return r }
       return .none
   }
}

It would be nice if the compiler could generate these style of accessors, with the payload available as an optional tuple. Perhaps it would be called `var stateFourData : (Error, T)?` or something predictable.

While I’m on the subject, sometimes I want every case of an enum to be its own type, which is a subtype of the enum’s type - e.g. MyEnum<Int>.stateThree.self. That would allow you to keep a collection of MyEnum values which are guaranteed to all be the same case.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution