Get rawValue from enum in a generic function

后端 未结 1 1957
慢半拍i
慢半拍i 2021-02-05 23:01

Update 8/28/2015: This will be solved in Swift 2

See Twitter response from Swift compiler developer

Update 10/23/2015: With Swi

相关标签:
1条回答
  • 2021-02-05 23:33

    Unfortunately, this doesn't look like it's possible in Swift at this point, but I've thought about your problem for a while, and I'll propose 3 ways that the Swift team could enable you to solve this problem.

    1. Fix the mirror for enums. The most straightforward solution is one that I'm sure you already tried. You're trying to build a reflection library, and you'd like to reflect an Any value to see if it's an enum, and if it is, you'd like to see if it has a raw value. The rawValue property should be accessible via this code:

      let mirror = reflect(theEnum) // theEnum is of Any type
      for i in 0..<mirror.count {
          if mirror[i].0 == "rawValue" {
              switch mirror[i].1.value {
              case let s as String:
                  return s
              case let csc as CustomStringConvertible:
                  return csc.description
              default:
                  return nil
              }
          }
      }
      

    However, this doesn't work. You'll find that the mirror has a count of 0. I really think that this is an oversight on the part of the Swift team in their implementation of Swift._EnumMirror, and I'll be filing a radar about this. rawValue is definitely a legitimate property. It is a weird scenario because enums aren't allowed to have other stored properties. Also, your enum's declaration never explicitly conforms to RawRepresentable, nor does it declare the rawValue property. The compiler just infers that when you type enum MyEnum: String or : Int or whatever. In my tests, it appears that it shouldn't matter whether the property is defined in a protocol or is an instance of an associated type, it should still be a property represented in the mirror.

    1. Allow for protocol types with defined associated types. As I mentioned in my comment above, it's a limitation in Swift that you cannot cast to a protocol type that has associated type requirements. You can't simply cast to RawRepresentable because Swift doesn't know what type the rawValue property will return. Syntax such as RawRepresentable<where RawValue == String> is conceivable, or perhaps protocol<RawRepresentable where RawValue == String>. If this were possible, you could try to cast to the type through a switch statement as in:

      switch theEnum {
      case let rawEnum as protocol<RawRepresentable where RawValue == String>:
         return rawEnum.rawValue
      // And so on
      }
      

    But that's not defined in Swift. And if you just try to cast to RawRepresentable, the Swift compiler tells you that you can only use this in a generic function, but as I look at your code, that's only led you down a rabbit-hole. Generic functions need type information at compile-time in order to work, and that's exactly what you don't have working with Any instances.

    The Swift team could change protocols to be more like generic classes and structs. For example MyGenericStruct<MyType> and MyGenericClass<MyType> are legitimately specialized concrete types that you can make a runtime check on and cast to. However, the Swift team may have good reasons for not wanting to do this with protocols. Specialized versions of protocols (i.e. protocol references with known associated types) still wouldn't be concrete types. I wouldn't hold my breath for this ability. I consider this the weakest of my proposed solutions.

    1. Extend protocols to conform to protocols. I really thought I could make this solution work for you, but alas no. Since Swift's built-in mirror for enums doesn't deliver on the rawValue, I thought why not implement my own generic mirror:

      struct RawRepresentableMirror<T: RawRepresentable>: MirrorType {
          private let realValue: T
      
          init(_ value: T) {
              realValue = value
          }    
      
          var value: Any { return realValue }
          var valueType: Any.Type { return T.self }
          var objectIdentifier: ObjectIdentifier? { return nil }
          var disposition: MirrorDisposition { return .Enum }
          var count: Int { return 1 }
      
          subscript(index: Int) -> (String, MirrorType) {
              switch index {
              case 0:
                  return ("rawValue", reflect(realValue.rawValue))
              default:
                  fatalError("Index out of range")
              }
          }
      
          var summary: String {
              return "Raw Representable Enum: \(realValue)"
          }
      
          var quickLookObject: QuickLookObject? {
              return QuickLookObject.Text(summary)
          }
      }
      

    Great! Now all we have to do is extend RawRepresentable to be Reflectable so that we can first cast theEnum as Reflectable (no associated type required) and then call reflect(theEnum) to give us our awesome custom mirror:

        extension RawRepresentable: Reflectable {
            func getMirror() -> MirrorType {
                return RawRepresentableMirror(self)
            }
        }
    

    Compiler error: Extension of protocol 'RawRepresentable' cannot have an inheritance clause

    Whaaaat?! We can extend concrete types to implement new protocols, such as:

        extension MyClass: MyProtocol {
            // Conform to new protocol
        }
    

    As of Swift 2, we can extend protocols to give concrete implementations of functions, such as:

        extension MyProtocol {
            // Default implementations for MyProtocol
        }
    

    I thought for sure we could extend protocols to implement other protocols, but apparently not! I see no reason why we couldn't have Swift do this. I think this would very much fit in with the protocol-oriented programming paradigm that was the talk of WWDC 2015. Concrete types that implement the original protocol would get a new protocol conformance for free, but the concrete type is also free to define its own versions of the new protocol's methods. I'll definitely be filing an enhancement request for this because I think it could be a powerful feature. In fact, I think it could be very useful in your reflection library.

    Edit: Thinking back on my dissatisfaction with answer 2, I think there's a more elegant and realistic possibility for working with these protocols broadly, and that actually combines my idea from answer 3 about extending protocols to conform to new protocols. The idea is to have protocols with associated types conforming to new protocols that retrieve type-erased properties of the original. Here is an example:

    protocol AnyRawRepresentable {
        var anyRawValue: Any { get }
    }
    
    extension RawRepresentable: AnyRawRepresentable {
        var anyRawValue: Any {
            return rawValue
        }
    }
    

    Extending the protocol in this way wouldn't be extending inheritance per se. Rather it would be just saying to the compiler "Wherever there is a type that conforms to RawRepresentable, make that type also conform to AnyRawRepresentable with this default implementation." AnyRawRepresentable wouldn't have associated type requirements, but can still retrieve rawValue as an Any. In our code, then:

    if let anyRawEnum = theEnum as? AnyRawRepresentable {  // Able to cast to
        let anyRawValue = anyRawEnum.anyRawValue  // anyRawValue is of type Any
        switch anyRawValue {
        case let s as String:
            return s
        case let csc as CustomStringConvertible:
            return csc.description
        default:
            return nil
        }
    }
    

    This kind of solution could be used broadly with any kind of protocol with associated types. I will likewise be including this idea in my proposal to the Swift team for extending protocols with protocol conformance.

    Update: None of the above options are available as of Swift 4. I did not receive a response as to why the Mirror on a RawRepresentable enum does not contain its rawValue. As for options #2 and #3, they are still within the realm of possibility for future releases of Swift. They have been mentioned on the Swift mailing list and in the Generics Manifesto document authored by Doug Gregor of the Swift team.

    The proper term for option #2 ("Allow for protocol types with defined associated types") is generalized existentials. This would allow protocols with associated types to possibly automatically return Any where there is an associated type or allow for syntax like the following:

    anyEnum as? Any<RawRepresentable where .RawValue == String>
    

    Option #3 has been mentioned occasionally on the mailing list, and it is a commonly rejected requested feature, but that is not to say it couldn't be included in future versions of Swift. In the Generics Manifesto, it is termed "Conditional conformances via protocol extensions". While recognizing its power as a feature, it sadly also states that efficient implementation is "nearly impossible."

    0 讨论(0)
提交回复
热议问题