> On Jun 9, 2016, at 4:55 PM, Dave Abrahams via swift-evolution > <swift-evolution@swift.org> wrote: > > > on Wed Jun 08 2016, Matthew Johnson <matthew-AT-anandabits.com > <http://matthew-at-anandabits.com/>> wrote: > >>> On Jun 8, 2016, at 1:33 PM, Dave Abrahams <dabrah...@apple.com> wrote: >>> >>> >>> on Tue Jun 07 2016, Matthew Johnson <matthew-AT-anandabits.com> wrote: >>> >> >>>>> On Jun 7, 2016, at 9:15 PM, Dave Abrahams <dabrah...@apple.com> wrote: >>>>> >>>>> >>>>> on Tue Jun 07 2016, Matthew Johnson <matthew-AT-anandabits.com >>>>> <http://matthew-at-anandabits.com/>> wrote: >>>>> >>>> >>>>>>> On Jun 7, 2016, at 4:13 PM, Dave Abrahams via swift-evolution >>>>>>> <swift-evolution@swift.org> wrote: >>>>>>> >>>>>>> >>>>>>> on Tue Jun 07 2016, Matthew Johnson <swift-evolution@swift.org> wrote: >>>>>>> >>>>>> >>>>>>>>> , but haven't realized >>>>>>>>> that if you step around the type relationships encoded in Self >>>>>>>>> requirements and associated types you end up with types that appear to >>>>>>>>> interoperate but in fact trap at runtime unless used in exactly the >>>>>>>>> right way. >>>>>>>> >>>>>>>> Trap at runtime? How so? Generalized existentials should still be >>>>>>>> type-safe. >>>>>>> >>>>>>> There are two choices when you erase static type relationships: >>>>>>> >>>>>>> 1. Acheive type-safety by trapping at runtime >>>>>>> >>>>>>> FloatingPoint(3.0 as Float) + FloatingPoint(3.0 as Double) // trap >>>>>>> >>>>>>> 2. Don't expose protocol requirements that involve these relationships, >>>>>>> which would prevent the code above from compiling and prevent >>>>>>> FloatingPoint from conforming to itself. >>>>>>> >>>>>>>> Or are you talking about the hypothetical types / behaviors people >>>>>>>> think they want when they don’t fully understand what is happening... >>>>>>> >>>>>>> I don't know what you mean here. I think generalized existentials will >>>>>>> be nice to have, but I think most people will want them to do something >>>>>>> they can't possibly do. >>>>>> >>>>>> Exactly. What I meant is that people think they want that expression >>>>>> to compile because they don’t understand that the only thing it can do >>>>>> is trap. I said “hypothetical” because producing a compile time error >>>>>> rather than a runtime trap is the only sane thing to do. Your comment >>>>>> surprised me because I can’t imagine we would move forward in Swift >>>>>> with the approach of trapping. >>>>> >>>>> I would very much like to be able to create instances of “Collection >>>>> where Element == Int” so we can throw away the wrappers in the stdlib. >>>>> That will require some type mismatches to be caught at runtime via >>>>> trapping. >>>> >>>> For invalid index because the existential accepts a type erased index? >>> >>> Exactly. >>> >>>> How do you decide where to draw the line here? It feels like a very >>>> slippery slope for a language where safety is a stated priority to >>>> start adopting a strategy of runtime trapping for something as >>>> fundamental as how you expose members on an existential. >>> >>> If you don't do this, the alternative is that “Collection where Element >>> == Int” does not conform to Collection. >> >> This isn’t directly related to having self or associated type >> requirements. It is true of all existentials. > > That is just an implementation limitation today, IIUC. What I'm talking > about here would make it impossible for some to do that. > >> If that changes for simple existentials and generalized existentials >> expose all members (as in the latest draft of the proposal) maybe it >> will be possible for all existentials to conform to their protocol. > > Not without introducing runtime traps. See my “subscript function” > example. > >> >>> That's weird and not very >>> useful. You could expose all the methods that were on protocol >>> extensions of Collection on this existential, unless they used >>> associated types other than the element type. But you couldn't pass the >>> existential to a generic function like >>> >>> func scrambled<C: Collection>(_ c: C) -> [C.Element] >>> >>>> IMO you should *have* to introduce unsafe behavior like that manually. >>> >>> Collection where Element == Int & Index == * >>> >>> ? >> >> I didn’t mean directly through the type of the existential. > > My question is, why not? That is still explicit. > >> One obvious mechanism for introducing unsafe behavior is to write >> manual type erasure wrappers like we do today. >> >> Another possibility would be to allow extending the existential type >> (not the protocol). This would allow you to write overloads on the >> Collection existential that takes some kind of type erased index if >> that is what you want and either trap if you receive an invalid index >> or better (IMO) return an `Element?`. I’m not sure how extensions on >> existentials might be implemented, but this is an example of the kind >> of operation you might want available on it that you wouldn’t want >> available on all Collection types. >> >>> >>>> Collection indices are already something that isn’t fully statically >>>> safe so I understand why you might want to allow this. >>> >>> By the same measure, so are Ints :-) >>> >>> The fact that a type's methods have preconditions does *not* make it >>> “statically unsafe.” >> >> That depends on what you mean by safe. Sure, those methods aren’t >> going corrupt memory, but they *are* going to explicitly and >> intentionally crash for some inputs. That doesn’t qualify as “fully >> safe” IMO. > > Please pick a term other than “unsafe” here; it's not unsafe in the > sense we mean the word in Swift. It's safe in exactly the same way that > array indexes and integers are. When you violate a precondition, it > traps. > > The user doesn't do anything “manual” to introduce that trapping > behavior for integers. Preconditions are a natural part of most types. > >>>> But I don’t think having the language's existentials do this >>>> automatically is the right approach. Maybe there is another approach >>>> that could be used in targeted use cases where the less safe behavior >>>> makes sense and is carefully designed. >>> >>> Whether it makes sense or not really depends on the use-cases. There's >>> little point in generalizing existentials if the result isn't very useful. >> >> Usefulness depends on your perspective. > > Of course. As I've said, let's look at the use cases. > >> I have run into several scenarios where they would be very useful >> without needing to be prone to crashes when used incorrectly. One >> obvious basic use case is storing things in a heterogenous collection >> where you bind . > > bind what? > >> >> >>> The way to find out is to take a look at the examples we currently have >>> of protocols with associated types or Self requirements and consider >>> what you'd be able to do with their existentials if type relationships >>> couldn't be erased. >>> >>> We have known use-cases, currently emulated in the standard library, for >>> existentials with erased type relationships. *If* these represent the >>> predominant use cases for something like generalized existentials, it >>> seems to me that the language feature should support that. Note: I have >>> not seen anyone build an emulation of the other kind of generalized >>> existential. My theory: there's a good reason for that :-). >> >> AFAIK (and I could be wrong) the only rules in the language that >> require the compiler to synthesize a trap except using a nil IUO, `!` >> on a nil Optional, and an invalid `as` cast . These are all >> syntactically explicit unsafe / dangerous operations. All other traps >> are in the standard library (array index, overflow, etc). Most >> important about all of these cases is that they have received direct >> human consideration. > > There is no distinction in the user model between what might be > synthesized by the language and what appears on standard library types. > >> Introducing a language (not library) mechanism that exposes members on >> generalized existentials in a way that relies on runtime traps for >> type safety feels to me like a pretty dramatic turn agains the stated >> priority of safety. It will mean you must understand exactly what is >> going on and be extremely careful to use generalized existentials >> without causing crashes. This will either make Swift code much more >> crashy or will scare people away from using generalized existentials >> (and maybe both). > > I don't accept either of those statements without seeing some analysis > of the use-cases. For example, I don't believe that AnyCollection et al > are particularly crash-prone. The likelihood that you'll use the wrong > index type with a collection is very, very low. I'm less certain of > what happens with Self requirements in real cases. > >> Neither of those outcomes is good. >> >> Collection indices are a somewhat special case as there is already a >> strong precondition that people are familiar with because it would be >> too costly to performance and arguably too annoying to deal with an >> Optional result in every array lookup. IMO that is why the library is >> able to get away with it in the current type erased AnyCollection. >> But this is not a good model for exposing any members on an >> existential that do not already have a strong precondition that causes >> a trap when violated. >> >> I think a big reason why you maybe haven’t seen a lot of examples of >> people writing type erased “existentials" is because it is a huge pain >> in the neck to write this stuff manually today. People may be >> designing around the need for them. I haven’t seen a huge sampling of >> type erased “existentials" other people are writing but I haven’t >> written any that introduce a trap like this. The only traps are in >> the “abstract" base class whose methods will never be called (and >> wouldn’t even be implemented if they could be marked abstract). >> >> What specific things do you think we need to be able to do that rely >> on the compiler synthesizing a trap in the way it exposes the members >> of the existential? > > I don't know. I'm saying, I don't think we understand the use-cases > well enough to make a determination. > >> Here are a few examples from Austin’s proposal that safely use >> existential collections. I don’t understand why you think this >> approach is insufficient. Maybe you could supply a concrete example >> of a use case that can’t be written with the mechanism in Austin’s >> proposal. >> >> https://github.com/austinzheng/swift-evolution/blob/az-existentials/proposals/XXXX-enhanced-existentials.md#associated-types-and-member-exposure >> >> <https://github.com/austinzheng/swift-evolution/blob/az-existentials/proposals/XXXX-enhanced-existentials.md#associated-types-and-member-exposure> >> >> <https://github.com/austinzheng/swift-evolution/blob/az-existentials/proposals/XXXX-enhanced-existentials.md#associated-types-and-member-exposure >> >> <https://github.com/austinzheng/swift-evolution/blob/az-existentials/proposals/XXXX-enhanced-existentials.md#associated-types-and-member-exposure>> >> >> let a : Any<Collection> >> >> // A variable whose type is the Index associated type of the underlying >> // concrete type of 'a'. >> let theIndex : a.Index = ... >> >> // A variable whose type is the Element associated type of the underlying >> // concrete type of 'a'. >> let theElement : a.Element = ... >> >> // Given a mutable collection, swap its first and last items. >> // Not a generic function. >> func swapFirstAndLast(inout collection: Any<BidirectionalMutableCollection>) >> { >> // firstIndex and lastIndex both have type "collection.Index" >> guard let firstIndex = collection.startIndex, >> lastIndex = collection.endIndex?.predecessor(collection) where >> lastIndex != firstIndex else { >> print("Nothing to do") >> return >> } >> >> // oldFirstItem has type "collection.Element" >> let oldFirstItem = collection[firstIndex] >> >> collection[firstIndex] = collection[lastIndex] >> collection[lastIndex] = oldFirstItem >> } >> >> var a : Any<BidirectionalMutableCollection where .Element == String> = ... >> >> let input = "West Meoley" >> >> // Not actually necessary, since the compiler knows "a.Element" is String. >> // A fully constrained anonymous associated type is synonymous with the >> concrete >> // type it's forced to take on, and the two are interchangeable. >> // However, 'as' casting is still available if desired. >> let anonymousInput = input as a.Element >> >> a[a.startIndex] = anonymousInput >> >> // as mentioned, this also works: >> a[a.startIndex] = input >> >> // If the collection allows it, set the first element in the collection to a >> given string. >> func setFirstElementIn(inout collection: Any<Collection> toString string: >> String) { >> if let element = string as? collection.Element { >> // At this point, 'element' is of type "collection.Element" >> collection[collection.startIndex] = element >> } >> } > > Neither of these look like they actually make *use* of the fact that > there's type erasure involved (and therefore should probably be written > as generics?). The interesting cases with Any<Collection...>, for the > purposes of this discussion, arise when you have multiple instances of > the same existential type that wrap different concrete types. > > Another problem I see: in this new world, what is the model for choosing > whether to write a function as a protocol extension/generic, or as a > regular function taking existential parameters? Given that either of > the above could have been written either way, we need to be able to > answer that question. When existentials don't conform to their > protocols, it seems to me that the most general thing to do is use > existentials whenever you can, and only resort to using generics when > forced by the type system. This does not seem like a particularly good > programming model to me, but I might be convinced otherwise. > > Anyway, my overall point is that this all seems like something we *can* > do and that nicely fills gaps in the type system, but not necessarily > something we *should* do until we better understand what it's actually > *for* and how it affects the programming model. >
playing with a different syntax. https://gist.github.com/lmihalkovic/8aa66542f5cc4592e967bade260477ef what’s missing is the syntax for opening such that it is possible to deal safely with the underlying concrete type (e.g. when dealing with 2 Equatable). But Doug had a nice strawman that can be streamlined. > -- > Dave > _______________________________________________ > swift-evolution mailing list > swift-evolution@swift.org <mailto:swift-evolution@swift.org> > https://lists.swift.org/mailman/listinfo/swift-evolution > <https://lists.swift.org/mailman/listinfo/swift-evolution>
_______________________________________________ swift-evolution mailing list swift-evolution@swift.org https://lists.swift.org/mailman/listinfo/swift-evolution