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