> On Jun 8, 2016, at 1:33 PM, Dave Abrahams <[email protected]> wrote:
> 
> 
> on Tue Jun 07 2016, Matthew Johnson <matthew-AT-anandabits.com> wrote:
> 
>>> On Jun 7, 2016, at 9:15 PM, Dave Abrahams <[email protected]> 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 
>>>>> <[email protected]> wrote:
>>>>> 
>>>>> 
>>>>> on Tue Jun 07 2016, Matthew Johnson <[email protected]> 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.  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.  

> 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.

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.

> 
>> 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.  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 .

> 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.  

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).  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?  

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>

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
    }
}


> 
> -- 
> Dave

_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to