Sent from my iPhone

> On Feb 20, 2017, at 5:51 PM, Anton Zhilin <antonyzhi...@gmail.com> wrote:
> 
> 2017-02-21 1:21 GMT+03:00 Matthew Johnson <matt...@anandabits.com>:
>> 
>> Thanks for the links.  I scanned through them somewhat quickly and didn’t 
>> see anything that specifically said `Never` should conform to all protocols. 
>>  Did you see that specifically?  I only saw mentions of it being a bottom 
>> type and therefore a subtype of all types, which I think is a bit different.
>> 
>> I think a big part of the confusion here revolves around the distinction 
>> between a type `T` being a subtype of another type `U` and `Type<T>` being a 
>> subtype of `Type<U>` (using the syntax in your metatype refactoring 
>> proposal).  I’m not an expert in this area, but I suspect that `Never` can 
>> be a subtype of all existential types but without requiring it to actually 
>> *conform* to all protocols.  Any non-instance protocol requirements are not 
>> available on existentials (afaik).
>  
> I didn't fully understand about metatypes, but otherwise yes indeed.
> 
>> Yes, I understood the example and it’s a good one.  What I’m wondering is 
>> what benefit you actually get from this.  There are two places where this 
>> default initializer could be used:
>> 
>> 1. In `seq` itself.  But it seems highly dubious to throw an error you know 
>> nothing about.  Why does `seq` need the ability to construct an error of the 
>> same type as a function given to it without knowing anything more about that 
>> error type.  Is there a use case for this?
>> 2. In callers of `seq`.  Why would the caller care if the error type that 
>> `seq` can throw has a default initializer?  Is there a use case for this?
>> 
>> In other words, why do you want to specify that the type of error that might 
>> be thrown must have a default initializer?  I can’t think of any possible 
>> circumstance where this would be valuable.
>> 
>> The same question can be asked of any other static requirements.  What are 
>> the use cases?  These seem highly theoretical to me.  Maybe I’m missing 
>> something, but if so I would like to see an example of how it is *used*, not 
>> just how you would need to write an extra overload without `rethrows`.
> 
> Seems highly theoretical to me as well.
> 
>> There is a potentially more practical benefit of keeping rethrows.  If a 
>> function is declared with `rethrows` we know that the function itself does 
>> not throw.  It only throws if one of its arguments throw when it invokes 
>> them.  This is a subtle but important difference.  For example, users 
>> calling a rethrowing function know that *they* have control over whether or 
>> not the call *actually* throws.  The caller might pass a couple of functions 
>> that *can* throw but in this particular case are known not to throw.  That 
>> could influence how the caller handles errors in the surrounding scope.
> 
> Agreed. Now I lean towards leaving the proposal as is.

I've been continuing to think through these generic rethrowing functions and 
have identified a problem I think we want to address before submitting a 
proposal for review.  Please hold off until I have a chance to write a post 
describing the problem and laying out our options.

> 
> 2017-02-21 1:21 GMT+03:00 Matthew Johnson <matt...@anandabits.com>:
>> 
>>> On Feb 20, 2017, at 11:14 AM, Anton Zhilin <antonyzhi...@gmail.com> wrote:
>>> 
>>> 2017-02-20 18:23 GMT+03:00 Matthew Johnson <matt...@anandabits.com>:
>>> 
>>> 
>>> 
>>>> 
>>>>> On Feb 20, 2017, at 3:58 AM, Anton Zhilin <antonyzhi...@gmail.com> wrote:
>>>>> But that raises another concern. In a previous discussion, it was taken 
>>>>> for granted that Never should conform to all protocols
>>>>> 
>>>> 
>>>> Do you have a pointer to this discussion?  I must have missed it.
>>> 
>>> 
>>> Here is the discussion where the idea of “empty” type originated.
>>> Some messages on the topic ended up being there.
>>> 
>>> This is the earliest mention of usage of this empty type for rethrows I 
>>> could find.
>>> Some related messages are here as well.
>>> 
>>> 
>>> We called this type NoReturn and meant it to be the bottom type, i.e. 
>>> subtype of all types, meaning that if you have an instance of 
>>> NoReturn—which can only happen in unreachable sections of code—then you can 
>>> convert it to any type. It should have worked like this:
>>> 
>>> func fatalError() -> Never
>>> 
>>> func divide(a: Int, b: Int) -> Int {
>>>     if b == 0 {
>>>         let n: Never = fatalError()
>>>         return n as Int
>>>     }
>>>     return a / b
>>> }
>>> I pushed the idea of replacing rethrows with Never, inspired by Haskell. 
>>> Although Haskell doesn’t have static function requirements and initializer 
>>> requirements.
>>> 
>>> 
>> 
>> Thanks for the links.  I scanned through them somewhat quickly and didn’t 
>> see anything that specifically said `Never` should conform to all protocols. 
>>  Did you see that specifically?  I only saw mentions of it being a bottom 
>> type and therefore a subtype of all types, which I think is a bit different.
>> 
>> I think a big part of the confusion here revolves around the distinction 
>> between a type `T` being a subtype of another type `U` and `Type<T>` being a 
>> subtype of `Type<U>` (using the syntax in your metatype refactoring 
>> proposal).  I’m not an expert in this area, but I suspect that `Never` can 
>> be a subtype of all existential types but without requiring it to actually 
>> *conform* to all protocols.  Any non-instance protocol requirements are not 
>> available on existentials (afaik).
>> 
>>> 
>>> 
>>>>> , because if one obtains an instance of Never (and they won’t), then 
>>>>> everything is possible. But now we say that Never can’t conform to 
>>>>> Default, because this would break its very invariant. Also it can’t 
>>>>> conform to any protocol with static members or initializers.
>>>>> 
>>>> 
>>>> It seems highly problematic to me to say that never conforms to any 
>>>> protocol with non-instance requirements.
>>> 
>>> 
>>> Here is an example with instance requirements only:
>>> 
>>> protocol MakesPizza {
>>>     func cook() -> Pizza
>>> }
>>> extension Never : MakesPizza {
>>>     func cook() -> Pizza {
>>>         // this method will never be called anyway
>>>         burnThisComputer()
>>>     }
>>> }
>>> 
>>> let maestroLaPizza = isHeAtWork ? validMaestro : (fatalError("something 
>>> went wrong") as MakesPizza)
>>> maestroLaPizza.cook()
>>> In this way, Never can conform to any protocol with only instance 
>>> requirements.
>>> 
>> 
>> Sure.
>> 
>>> 
>>> 
>>>>> But then basically, Never trick can’t be used when we request anything 
>>>>> more than Error from generic error type (with static members or 
>>>>> initializers). So this approach turns out to be more limiting than 
>>>>> rethrows.
>>>>> 
>>>> 
>>>> Can you elaborate here?  If you require a function to throw an error type 
>>>> that has non-instance requirements then you would necessarily be 
>>>> restricting callers to provide a throwing function.  It is not possible to 
>>>> express such a function with `rethrows`.  You can’t talk about the error 
>>>> type at all.  If you could talk about the error type and were able to 
>>>> constrain it in this way `rethrows` would necessarily have to exhibit the 
>>>> same behavior as the generic version.  The behavior arises out of the 
>>>> constraint you are applying, not the mechanism by which you forward the 
>>>> type.
>>> 
>>> 
>>> With rethrows approach:
>>> 
>>> protocol BaseError : Error {
>>>     init()
>>> }
>>> 
>>> func seq<E1, E2>(f: () throws(E1) -> (), g: () throws(E2) -> ()) 
>>> rethrows(BaseError)
>>>      where E1: BaseError, E2: BaseError { ... }
>>> With Never approach, we have to create two separate functions for the same 
>>> effect, because Never does not fit in BaseError:
>>> 
>>> func seq<E1, E2>(f: () throws(E1) -> (), g: () throws(E2) -> ()) 
>>> throws(BaseError)
>>>      where E1: BaseError, E2: BaseError {
>>>     // It never actually throws E1() or E2() itself, but this fact can't be 
>>> reflected in the signature
>>> }
>>> 
>>> func seq(f: () -> (), g: () -> ()) {
>>>     // repeat the body
>>> }
>>> That’s where loss of information (which I meantioned earlier) hurts: we 
>>> can’t apply magic and say “if E1 and E2 are Never then seq does not throw. 
>>> Because it can throw anyway.
>>> 
>>> Well, I’m just repeating myself, at least I gave a bit more complete 
>>> example :)
>>> 
>> 
>> Yes, I understood the example and it’s a good one.  What I’m wondering is 
>> what benefit you actually get from this.  There are two places where this 
>> default initializer could be used:
>> 
>> 1. In `seq` itself.  But it seems highly dubious to throw an error you know 
>> nothing about.  Why does `seq` need the ability to construct an error of the 
>> same type as a function given to it without knowing anything more about that 
>> error type.  Is there a use case for this?
>> 2. In callers of `seq`.  Why would the caller care if the error type that 
>> `seq` can throw has a default initializer?  Is there a use case for this?
>> 
>> In other words, why do you want to specify that the type of error that might 
>> be thrown must have a default initializer?  I can’t think of any possible 
>> circumstance where this would be valuable.
>> 
>> The same question can be asked of any other static requirements.  What are 
>> the use cases?  These seem highly theoretical to me.  Maybe I’m missing 
>> something, but if so I would like to see an example of how it is *used*, not 
>> just how you would need to write an extra overload without `rethrows`.
>> 
>> There is a potentially more practical benefit of keeping rethrows.  If a 
>> function is declared with `rethrows` we know that the function itself does 
>> not throw.  It only throws if one of its arguments throw when it invokes 
>> them.  This is a subtle but important difference.  For example, users 
>> calling a rethrowing function know that *they* have control over whether or 
>> not the call *actually* throws.  The caller might pass a couple of functions 
>> that *can* throw but in this particular case are known not to throw.  That 
>> could influence how the caller handles errors in the surrounding scope.
>> 
>> 
>> 
>> 
> 
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to