[swift-evolution] Allowing non-binding pattern matching as a Bool expression?

plx plxswift at icloud.com
Thu Dec 10 10:29:44 CST 2015


> On Dec 10, 2015, at 9:48 AM, Kevin Wooten <kdubb at me.com> wrote:
> 
> 
>> On Dec 10, 2015, at 8:42 AM, plx via swift-evolution <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
>> 
>> The motivating/most-common case for me is serialization:
>> 
>> coder.encodeInteger(self.enumerationCase.rawValue, forKey: “case")
>> switch self {
>>   case let .Foo(data): coder.encodeX(data, forKey: “data”)
>>   case let .Bar(data): coder.encodeY(data, forKey: “data”)
>> }
>> 
> 
> Solvable now via…
> 
> switch self {
>   case let .Foo(data):
>     coder.encodeInteger(…)
>     coder.encodeX(..)
>   case let .Bar(data):
>     coder.encodeInteger(…)
>     coder.encodeY(…)
> }
> 
> It’s only missing an integer raw value assigned by the compiler.

Well, it’s also missing:

- a compiler-enforced exhaustiveness check here:

guard let enumerationCase = EnumerationCase(rawValue: coder.decodeIntegerForKey(“case”)) else { return nil }
switch enumerationCase {
  case IntegerForFooCase: …
  case IntegerForBazCase: …
  // forgot about Bar! and Quux!
}

…along with compiler-enforced “no typo/no-duplicate/no-forget” checks here:

// in encode:
case let .Foo(data):
  coder.encodeInteger(1, ..)
  coder.encodeX(…)
case let .Bar(data):
  coder.encodeInteger(1, ..) // oopsie
  coder.encodeY(…)
case let .Baz(data):
  coder.encodeInteger(3, ..)
  coder.encodeZ(…)
case let .Quux(data):
  // oopsie! if only i didn’t have to repeat myself!
  coder.encodeW(…)

// in decode:
switch caseInteger {
  case 1: // try to decode Bar // oopsie
  case 2: // try to decode Foo // oopsie
  case 4: // try to decode Baz (//oopsie!)
    // oopsie, forgot about Quux
}

…and once I’m doing this:

extension Example {
  static let SerializationIntegerForFoo: Int = 1
  static let SerializationIntegerForBar: Int = 2
  // etc.

  var caseSerializationInteger: Int { 
    get {
      switch self {
        case .Foo(_): return .SerializationIntegerForFoo // etc.
      }
    }
  }
}

…we’re back to the same boilerplate, just written manually and without, e.g., the benefit of exhaustiveness checks in our switch statements.

Outside serialization, the construct has some miscellaneous handiness, e.g.:

enum SomeStateMachineStateData<A,B,C> {
  case Initialized
  case StateA(A)
  case StateB(B)
  case StateC(C)
  case MixedAB(A,B)
}

// ^ *not* obj-c visible

@objc
enum SomeStateMachineState : Int {
  case Initialized
  case StateA
  case StateB
  case StateC
  case MixedAB
}

// ^ objc-visible

…(and similar) which makes it easier to, say, have an obj-c delegate protocol `SomeStateMachineDelegate` that can “follow along” with the associated state machine’s transitions.

Anyways, this isn’t a request that would make previously-unsolvable problems solvable, it’s merely a request for some compiler assistance reducing certain types of boilerplate, offered up in a discussion of other possible enumeration-related boilerplate reduction (isX(), projectX(), etc.).

On an importance scale from 1-10 it’s maybe a 3 for me personally, but I’d hate to see e.g. synthesis of `isX()` make it into the language without having at least seen if anyone else is interested on this one as well.

> 
>> …etc., and the obvious use of the above when decoding (decode the case, switch on it, decode data).
>> 
>> Once you have it it’s occasionally handy in other contexts, but that’s by far the main motivation.
>> 
>>> On Dec 10, 2015, at 9:35 AM, Kevin Wooten <kdubb at me.com <mailto:kdubb at me.com>> wrote:
>>> 
>>> I get the gist of what you are requesting and I can see it might me useful but given that
>>> 
>>> if case .Foo(_) == ex {
>>> }
>>> 
>>> Provides comparison, regardless of the # of associated values in .Foo, I’m not sure generating a second enum just to carry a diminished amount of information is worth it.
>>> 
>>> 
>>>> On Dec 10, 2015, at 8:24 AM, plx via swift-evolution <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
>>>> 
>>>> FWIW, as long as we’re asking for some compiler assistance generating useful enumeration-related boilerplate, I’d like to request that something along these lines be possible in some version of Swift:
>>>> 
>>>> @synthesize_case_enum
>>>> enum Example {
>>>> 
>>>>   case Foo(X)
>>>>   case Bar
>>>>   case Baz(Y)
>>>> 
>>>> }
>>>> 
>>>> …which would then by default expand to something like this:
>>>> 
>>>> enum ExampleCase : Int {
>>>>   
>>>>   case Foo
>>>>   case Bar
>>>>   case Baz
>>>> 
>>>> }
>>>> 
>>>> extension Example {
>>>>  
>>>>   var enumerationCase: ExampleCase {
>>>>     get {
>>>>       switch self {
>>>>         case .Foo(_): return .Foo
>>>>         case .Bar(_): return .Bar
>>>>         case .Baz(_): return .Baz
>>>>       }
>>>>     }
>>>>   }  
>>>> 
>>>> }
>>>> 
>>>> …in the default case, but with some customization of the synthesized enumeration-case-enum’s details.
>>>> 
>>>> For the remaining proposals:
>>>> 
>>>> - `isX()` is nice (and should always `-> Bool`, IMHO)
>>>> - `projectX()` is nice and should:
>>>>   - *not* be defined for enums w/out associated values
>>>>   - `-> T?` for enums w/ a single associated value
>>>>   - -> the full tuple, for enums w/ multiple associated values, e.g.:
>>>>     - `Foo(A,B)` yields `projectFoo() -> (A,B)?
>>>>     - `Event(place: Place, time: Time)` -> (place: Place, time: Time)?
>>>>     - (which is essentially the same as for the single-value case, just making it explicit)
>>>> 
>>>> …which has the benefit of predictability (and if you need more convenience, replace your tuples with actual classes-or-structs). 
>>>> 
>>>> Finally, as convenient as the `associatedValue` proposal is down below, it seems better-served by some sort of generalized delegation construct; at a minimum, I don’t think it's that uncommon to have N cases all wrapping the same underlying type, and in such cases working with an `Any?` would feel rather clunky.
>>>> 
>>>>> On Dec 9, 2015, at 3:00 PM, Andrew Bennett via swift-evolution <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
>>>>> 
>>>>> I really like this solution and perhaps you can also go @projectable("empyValue") if the default name is a bad choice. See @obc for similar usage.
>>>>> 
>>>>> On Thursday, 10 December 2015, Alex Lew via swift-evolution <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
>>>>> Semantically, I believe enums with more than one associated value are actually just enums with one associated value, of tuple type.
>>>>> 
>>>>> I'm not convinced it would be bad to do magic function generation, but it's worth considering explicit syntax. If we were to use @ compile-time attributes, maybe it could be a word before the case -- like @projectable or @selectable (or some better name)?
>>>>> 
>>>>> enum List<T> {
>>>>>      @projectable case Empty
>>>>>      indirect case FirstAndRest(T, List<T>)
>>>>> }
>>>>> 
>>>>> generates:
>>>>> myList.isEmpty() -> Bool
>>>>> 
>>>>> Another option: some sort of @reflectable attribute on the enum itself.
>>>>> 
>>>>> @reflectable enum Pet {
>>>>>      case Cat(name: String, color: String)
>>>>>      case Dog(name: String, breed: String)
>>>>>      case Bear(name: String, isHibernating: Bool)
>>>>> }
>>>>> 
>>>>> And one other option, in a very different direction, that seems weird but maybe has its benefits:
>>>>> 
>>>>> What if we exposed an associatedValue computed property of type Any? (It would be unit type for cases with no associated value.)
>>>>> 
>>>>> You could then do something like this:
>>>>> 
>>>>> enum Contact {
>>>>>      case Person(String) // name
>>>>>      case Business(String) // org name
>>>>>      case FamilyMember(String) // relation name
>>>>>      case RecentCall(Int) // a contact from caller id, only store the phone number
>>>>> 
>>>>>      func name() -> String {
>>>>>           return associatedValue as? String ?? "Unknown (\(associatedValue as! Int))"
>>>>>      }
>>>>> }
>>>>> 
>>>>> Essentially, it allows you to project out a relatively common associated value (in this case a string) without much boilerplate. It's also just one thing for the compiler to generate, instead of n. Not crazy about any of these... just brainstorming. It may also be that a concise switch-like control flow expression eliminates the need for this.
>>>>> 
>>>>> -Alex
>>>>> 
>>>>> On Wed, Dec 9, 2015 at 11:49 AM, thorsten at portableinnovations.de <javascript:_e(%7B%7D,'cvml','thorsten at portableinnovations.de');> <thorsten at portableinnovations.de <javascript:_e(%7B%7D,'cvml','thorsten at portableinnovations.de');>> wrote:
>>>>> I would prefer if no "magic" methods would be generated automatically, but only when marked with @derivestandardmethods (fill in better name here).
>>>>> 
>>>>> As to naming I like the proposal #1 by Alex.
>>>>> 
>>>>> What about enums with more than one associated value?
>>>>> 
>>>>> -Thorsten
>>>>> 
>>>>> Am 09.12.2015 um 07:29 schrieb Alex Lew via swift-evolution < swift-evolution at swift.org <javascript:_e(%7B%7D,'cvml','swift-evolution at swift.org');>>: 
>>>>> 
>>>>>> Thanks, Chris, for all the time you're putting into responding to these proposals (and the kindness with which you're responding!). I really like that solution.
>>>>>> 
>>>>>> Brainstorming some names for the auto-generated functions:
>>>>>> 
>>>>>> 1. If a case has no associated values, isX() -> Bool is generated, where X is the case name.
>>>>>>     If a case has an associated value of type T, asX() -> T? is generated, where X is the case name.
>>>>>>     This mirrors is/as? operators, which return Bool / Optional respectively.
>>>>>> 2. projectX() -> Bool / projectX() -> T?
>>>>>> 3. isX() -> Bool / xValue() -> T?
>>>>>> 
>>>>>> Another option (probably the wrong option, but it's worth putting out there) is that instead of returning Bool in the no-associated-value case, we return ()?. This would make me feel better about using the same naming convention (asX(), projectX(), xValue(), etc.) for each case, and would allow for != nil checks on all cases. But it would probably be a little confusing for newcomers to the language.
>>>>>> 
>>>>>> One (potentially misguided) question. I noticed in proposal 0002 (on removing function currying) that there are "plans to move away from the arguments-are-a-single-tuple model" in the near future. Would this also affect associated values of enums? That is, might
>>>>>> 
>>>>>> case Dog(name: String, age: Int, breed: String)
>>>>>> 
>>>>>> one day not have the semantics of a single associated value of type (name: String, age: Int, breed: String)? Or is the de-ML-ification planned only for function arguments?
>>>>>> 
>>>>>> -Alex
>>>>>> 
>>>>>> On Wed, Dec 9, 2015 at 12:45 AM, Chris Lattner <clattner at apple.com <javascript:_e(%7B%7D,'cvml','clattner at apple.com');>> wrote: 
>>>>>> 
>>>>>> > On Dec 7, 2015, at 8:05 PM, Alex Lew via swift-evolution <swift-evolution at swift.org <javascript:_e(%7B%7D,'cvml','swift-evolution at swift.org');>> wrote:
>>>>>> >
>>>>>> > Hi all,
>>>>>> >
>>>>>> > Curious to hear thoughts on allowing non-binding pattern matches to be used as boolean values outside of an if, guard, for...in, while, switch, etc. Something like:
>>>>>> >
>>>>>> > enum List<T> {
>>>>>> >      case Empty
>>>>>> >      indirect case Link(T, List<T>)
>>>>>> >
>>>>>> >      func isEmpty() -> Bool {
>>>>>> >           return case .Empty = self
>>>>>> >      }
>>>>>> > }
>>>>>> 
>>>>>> I agree with you that this is a problem that we should solve, but I think it could be solved in a different way.  Imagine if: 
>>>>>> 
>>>>>> enum Foo { 
>>>>>>   case X(a : Float), Y, Z(a : Int) 
>>>>>> } 
>>>>>> 
>>>>>> automatically synthesized these members (the exact names are just a strawman proposal, not serious :-) 
>>>>>> 
>>>>>> extension Foo { 
>>>>>>   func isX() -> Float? {…} 
>>>>>>   func isY() -> Bool {…} 
>>>>>>   func isZ() -> Int? {…} 
>>>>>> } 
>>>>>> 
>>>>>> This would tie into all of the mechanics we have for dealing with optionals, e.g. if/let and ?? 
>>>>>> 
>>>>>> -Chris
>>>>>> 
>>>>> 
>>>>>> _______________________________________________ 
>>>>>> swift-evolution mailing list 
>>>>>> swift-evolution at swift.org <javascript:_e(%7B%7D,'cvml','swift-evolution at swift.org');> 
>>>>>> https://lists.swift.org/mailman/listinfo/swift-evolution <https://lists.swift.org/mailman/listinfo/swift-evolution> 
>>>>> Untracked with Trackbuster <https://trackbuster.com/?sig>
>>>>> 
>>>>>  _______________________________________________
>>>>> swift-evolution mailing list
>>>>> swift-evolution at swift.org <mailto:swift-evolution at swift.org>
>>>>> https://lists.swift.org/mailman/listinfo/swift-evolution <https://lists.swift.org/mailman/listinfo/swift-evolution>
>>>> 
>>>> 
>>>> _______________________________________________
>>>> swift-evolution mailing list
>>>> swift-evolution at swift.org <mailto:swift-evolution at swift.org>
>>>> https://lists.swift.org/mailman/listinfo/swift-evolution <https://lists.swift.org/mailman/listinfo/swift-evolution>
>>> 
>> 
>> 
>> _______________________________________________
>> swift-evolution mailing list
>> swift-evolution at swift.org <mailto:swift-evolution at swift.org>
>> https://lists.swift.org/mailman/listinfo/swift-evolution
> 

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20151210/e1588275/attachment.html>


More information about the swift-evolution mailing list