2017-02-21 1:21 GMT+03:00 Matthew Johnson <[email protected]>: > > 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. 2017-02-21 1:21 GMT+03:00 Matthew Johnson <[email protected]>: > > On Feb 20, 2017, at 11:14 AM, Anton Zhilin <[email protected]> wrote: > > 2017-02-20 18:23 GMT+03:00 Matthew Johnson <[email protected]>: > > > >> On Feb 20, 2017, at 3:58 AM, Anton Zhilin <[email protected]> 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 > <http://discourse.natecook.com/t/idea-change-noreturn-func-f-to-func-f-noreturn/1000> > is the discussion where the idea of “empty” type originated. > Some messages on the topic ended up being there > <http://discourse.natecook.com/t/idea-repurpose-void/1406>. > > This <http://discourse.natecook.com/t/idea-repurpose-void/1406> is the > earliest mention of usage of this empty type for rethrows I could find. > Some related messages are here > <http://discourse.natecook.com/t/draft-change-noreturn-to-unconstructible-return-type/1765/16> > 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 [email protected] https://lists.swift.org/mailman/listinfo/swift-evolution
