[swift-evolution] ValueEnumerable protocol with derived implementation for enums

John McCall rjmccall at apple.com
Sat Apr 23 00:50:24 CDT 2016


> On Apr 22, 2016, at 10:18 PM, Jacob Bandes-Storch <jtbandes at gmail.com> wrote:
> On Sat, Apr 16, 2016 at 5:20 AM, plx via swift-evolution <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
> 
> 
> My 2c is that if this is to go in the standard library, it should be done “right”, which would be more like this version of it:
> 
> protocol ValueEnumerable {
>   associatedtype ValueCollection : Collection where ValueCollection.Iterator.Element == Self
>   static var allValues: ValueCollection
> }
> 
> …and that this concept should simply *wait* for that feature to be available before going into the standard library. 
> 
> The reason I say this is simply b/c it sounds like this proposal wants to be able to support more than simple enumerations, in which case having some flexibility in the representation seems appropriate. Consider e.g.:
> 
>   struct AxisPolicy3D<Policy:protocol<Equatable,ValueEnumerable>> {
>     var x: Policy
>     var y: Policy
>     var z: Policy
>   }
> 
>   extension AxisPolicy3D : ValueEnumerable {
> 
>     static let allValues: ValueCollection = product(Policy.allValues,Policy.allValues,Policy.allValues).lazy.map() { 
>         (x,y,z) 
>         in
>         AxisPolicy3D(x: x, y: y, z: z)
>     }
> 
>   }
> 
> …and similar, wherein the cost of *requiring* an array here could become rather large.
> 
> But I have a couple general concerns here:
> 
> # Resiliency 
> 
> My understanding is that the design for resiliency vis-a-vis enumerations is meant to allow enumerations to have cases added in future revisions (and perhaps also private cases? I didn’t follow resiliency closely). 
> 
> If that’s right, and this protocol is supposed to go into the standard library, it might also need to address such issues. I have no help to offer and would love to be wrong about this point.
> 
> Thank you for bringing this up; I hadn't thought about it. Indeed, the library evolution design document <http://jrose-apple.github.io/swift-library-evolution/#enums <http://jrose-apple.github.io/swift-library-evolution/#enums>> states that adding new cases, adding raw types, and reordering cases should be binary-compatible changes.
> 
> I hope someone who knows more about the resilience design can weigh in here. I'll CC Jordan Rose and John McCall, authors of that document, on this email.
> 
> I think you're right that the implications of requiring an array might be significant, if this array is exported as public API in a module which other binaries depend on. So I wonder if it might be possible to pursue a solution which doesn't export any additional public API in a module.
> 
> Recall that we'd like to be able to add ValuesEnumerable support in an extension, both on Swift enums and on enums imported from Obj-C. Seems like you might not want those conformances to be exported, so that future changes in the type of allValues wouldn't have to break existing compiled binaries. (But currently, IIUC, extensions which add protocol conformances must be public.)
> 
> I'm almost wondering whether we should be doing something like #allValues(MyEnum), which uses # to indicate "compiler magic" (for now it would produce an Array<MyEnum>), gathering the available cases from the module at compile time. At some time in the future, when reflection is much more mature, perhaps this could be replaced with a standard library function.
> 
> ---
> 
> This also prompted me to research Java's implementation a bit more. I'm not a Java user, let alone expert, but here's what I found:
> 
> Class.getEnumConstants() returns the values in source order.  <https://docs.oracle.com/javase/tutorial/reflect/special/enumMembers.html <https://docs.oracle.com/javase/tutorial/reflect/special/enumMembers.html>> The page also says the following: 
> 
> Note: For various reasons, including support for evolution of the enum type, the declaration order of enum constants is important. Class.getFields() and Class.getDeclaredFields() do not make any guarantee that the order of the returned values matches the order in the declaring source code. If ordering is required by an application, use Class.getEnumConstants().
>  
> There's also a section on "Evolution of Enums" in this page about Binary Compatibility: <https://docs.oracle.com/javase/specs/jls/se7/html/jls-13.html#jls-13.4.26 <https://docs.oracle.com/javase/specs/jls/se7/html/jls-13.html#jls-13.4.26>>  "Adding or reordering constants in an enum type will not break compatibility with pre-existing binaries."
> 
> Point being that getEnumConstants() always returns an array, it just might have different things in it depending on the version of the class you're interrogating.
>  
> 
> # Other Remarks
> 
> I see the `CaseEnumerable` discussion in the other discussion. It’s certainly related, but it’s something with enough independent utility I wouldn’t want it to get “lost” in this topic (especially since I think this topic is a great feature for the language, but one that may be awhile coming). 
> 
> CaseEnumerable was just an earlier name for Value(s)Enumerable. The stuff in the "Future directions" section remains speculative. I think we should keep the proposal focused if we want it to ever happen; improvements can come later.

I have not been following this discussion, but I would be extremely antsy about guaranteeing any particular representation for the set of values.  Guaranteeing a contiguous array implementation seems like a really bad idea, especially if that's taken to mean that we're going to actually provide a static global array.  But there's no way to avoid providing a public API, because a public conformance itself implies a public API with some level of corresponding overhead.

I don't remember the details of Java enums from my days as a Java programmer, but reading between the lines of your description, it sounds to me like Java originally made overly-strong guarantees that it decided to walk back in a later release.  That's a lesson we should heed.

The interaction of resilience with enums is in principle quite straightforward: you ought to be able to arbitrarily change the set of stored cases for a resilient enum.  That includes adding cases, changing existing cases to be "computed", and so on.  (We haven't yet designed what it ought to mean for a case to be computed, but I assume it at least means providing an injector (Payload -> Enum) and a projector (Enum -> Payload?); whether and how to allow computed cases to factor into exhaustiveness checking is a separate but crucial question.)  The fundamental problem for features like this is that adding a case with a payload is not compatible with actually being enumerable, outside of special cases and/or some formal-but-useless notion of recursive enumerability.  But even if you couldn't add new cases with payloads (which is something we might consider adding as an intermediate opt-in constraint), and thus the type was necessarily finite, I can't imagine wanting to promise to return a static global array.

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


More information about the swift-evolution mailing list