The real beauty of this added mechanism is when you consider generics. It would be easy to write a generic function that operates on both throwing and non-throwing parameters, while preserving their throwing-ness. Think `rethrows` except not limited to immediate nonescaping calls. This part needs some serious thought, but having throwing functions play nice with generics would be spectacular.
> On May 1, 2017, at 1:53 PM, Gor Gyolchanyan <g...@gyolchanyan.com> wrote: > > I guess I failed to communicate my thoughts properly. My bad. > You’re right, this is a completely orthogonal issue and you're right: this > does take nothing more then a new enum type and syntax sugar on top of it. > > My first guess is to allow this: > > func foo() throws -> Int { > guard myCondition else { > throw EmbarrassingError.oops > } > return 42 > } > > let failable: Failable<Int> = catch foo() > > func bar() throws -> String { > let int = try failable > return “\(int)" > } > > This doesn’t intersect with any existing syntax, so it should be additive. > >> On May 1, 2017, at 1:15 PM, Xiaodi Wu <xiaodi...@gmail.com> wrote: >> >>> On Mon, May 1, 2017 at 2:58 AM, Gor Gyolchanyan <g...@gyolchanyan.com> >>> wrote: >>> I have read those documents before, but It’s worth re-reading them to see >>> if I missed something, but I’l still explain my motivation and seek >>> arguments against the postulated problem (rather then a specific solution). >>> >>> (a) There are different types of error. >>> >>> Yes, there are different types of error in Swift, which require different >>> reactions from the programmer. >>> If I’m not missing something, the three main types of error in Swift are: >>> - Simple encapsulatable errors that are expected to be treated as normal >>> values until the time comes for someone to take care of them by unpacking >>> the content. >>> - Automatic propagatable errors that require the programmer to either >>> handle the error immediately or propagate it by delegating to its own >>> caller. >>> - Fatal errors, which represent logic errors and broken invariants and >>> preconditions, which are purely a programmer error and should not be dealt >>> with dynamically, hence the terminated process with a message. >>> >>> (b) The programmer is expected to react differently to different types of >>> error. >>> >>> Yes, and the three main ways a programmer is expected to react to the an >>> error are: >>> - If it’s an optional, they’re encouraged to store and pass it around >>> freely until someone down the line decides to unpack it and deal with the >>> possibility that it isn’t there. >>> - If it’s an error, they’re encouraged to either handle it on the spot or >>> declare themselves throwing and delegate the responsibility to the caller. >>> - Look at the standard output and figure out why the fatal error occurred, >>> perhaps with the help of the debugger. >>> >>> (c) The language is a tool to help the programmer react. >>> >>> Yes, that comes in the form of three language constructs: >>> - Optionals, which allow storing a union of a value and its absence (for >>> an undefined and hopefully obvious reason). >>> - Throwing functions, which allow making sure that the error will be >>> handled as soon as possible. >>> - Fatal errors, which allow the programmer to mark points in code which >>> should never be reached in a correct system in order to keep the logic from >>> going AWOL in case the programmer screwed up somewhere. >>> >>> (d) Optionals and errors are not unified, and unification is a non-goal, >>> because they are designed to help the programmer react differently to >>> different types of error. >>> >>> Yes, and those different types of error with different reactions are all >>> valid and shouldn’t be unified. >>> My point is that the language should make it easy for a programmer to >>> transition from one type of error to another, because the same error has >>> different severity in different contexts. >>> For instance, a “file not found” error when trying to open a file handler >>> is not critical at all in the context of the file opening function, because >>> it’s a perfectly expected outcome of the operation. >>> However, for a module that handles loading critical data from disk (like >>> encryption keys needed to decrypt the manipulated content) it is a critical >>> error that cannot be dealt with. >>> In this case it deserves to be a fatal error, because the programmer didn’t >>> bother to implement a logic for creating the missing file or properly >>> notifying the user of the lacking permissions to do so. >>> Conversely, some errors start as being urgent (like a JSON parser that >>> throws an error when it encounters invalid syntax), but become less urgent >>> for the client (a JSON editor that simply displays the error message). >> >> Again, that's not my understanding for the rationale behind having both >> Optional return values and errors. It's not that one is more "urgent" or >> "important" than the other. An Optional is used when something can only fail >> in one obvious way; an error is thrown when it can fail in multiple, but >> recoverable, ways. You can care a lot about a nil value and not at all about >> an error, or vice versa. >> >> >>> As for my use case: >>> >>> I have a JSON parser that may throw, and I have a a JSON Editor class that >>> allows editing JSON files as well as displaying the parsing errors. >>> I have a malformed JSON file that I open in the editor. The JSON parser >>> throws an error, which should be caught and stored somewhere for the editor >>> to display. >>> I have file reader that reads a file in some encoding and returns an >>> optional string with the file contents (nil means file couldn’t be read or >>> the encoding is wrong). >>> >>> For the JSON parser, a malformed JSON file is an obvious error, but for the >>> editor, it’s a perfectly valid and expected condition, which doesn’t >>> deserve to be an error. >>> Therefore, the thrown error of the JSON parse has to be caught and >>> encapsulated indiscriminately to demote it from an error to a return value. >>> Conversely, the returned nil form the file reader is perfectly valid and >>> expected condition, but for the editor, it’s an error. >>> Therefore, the returned nil should be checked and converted to an error >>> that will be thrown to promote it to a full-fledged error. >>> >>> I would want to have a way to easily promote/demote different types of >>> errors to accommodate the changing perception of their urgency. >>> For instance, by being able to throw an optional, thus introducing a new >>> way of unpacking it (by promoting it to an error). Currently, it is by >>> manually unpacking the optional, deciding what error to throw and throwing >>> it manually. >>> Or, being able to catch an error into an optional, thus introducing a new >>> way of handling it (by demoting it to an optional). There is a way to do >>> that currently in the form of `try?` and `try!`, but their downside is that >>> they are lossy (losing the error itself). >>> >>> All I want is for the language to help me losslessly catch errors into >>> something like an optional, except with the error intact with the >>> possibility of easily re-throwing it in the future. >> >> This is an orthogonal concern to whether to model an error with an Optional. >> Optional is used when there's only one obvious (and recoverable) way of >> failing; throwing an error is used when there are multiple recoverable ways >> of failing. As Rod mentions, the _point_ of an Optional is that it doesn't >> come with an associated Error type. Every Optional.none compares equal to >> every other Optional.none, even when the wrapped type differs. Obviously, >> this means that there's no error to propagate manually or automatically, but >> whether or not an error propagates automatically is not the reason to use or >> not to use Optional to model your error. Likewise, the _point_ of using >> `try!` (not a downside) is to _lose_ the error, not just to stop the >> propagation of it. >> >> By contrast, your use case is about _storing_ an error, which is totally the >> opposite of what Optional does. It sounds like you don't like how errors are >> designed to automatically propagate (albeit with marking at the origination >> site), and you want to manually propagate errors using a Result<T, Error> >> type instead. I guess this is what you mean by errors being "urgent" and >> wanting to "demote" it. I wouldn't disagree that it's worth thinking about a >> syntax to offer some additional control over that. Suppose, for instance, we >> had a new type: >> >> ``` >> enum Result<Wrapped, Error> { >> case some(Wrapped) >> case none(Error) >> } >> ``` >> >> We could invent a new operator `try*`, which returns a `Result` instead of >> throwing. And then we could invent new sugar; perhaps, `result?` would be >> sugar for `result.some` and `result*` would be sugar for `result.none`. I'm >> not proposing this, but I can see how something along these lines would give >> you more options. >> >> >>> This would also solve the problem of multiple throwing calls having >>> different urgency to them and being forced to write a lot of boilerplate to >>> catch their errors individually and deal with them separetely. >>> >>>> On May 1, 2017, at 1:44 AM, Xiaodi Wu <xiaodi...@gmail.com> wrote: >>>> >>>>> On Sun, Apr 30, 2017 at 5:05 PM, Gor Gyolchanyan <g...@gyolchanyan.com> >>>>> wrote: >>>>> >>>>>> On May 1, 2017, at 12:10 AM, Xiaodi Wu <xiaodi...@gmail.com> wrote: >>>>>> >>>>>> You may wish to read the rationale behind the current error handling >>>>>> design: >>>>>> >>>>>> https://github.com/apple/swift/blob/master/docs/ErrorHandlingRationale.rst >>>>>> >>>>>> A Result type like you suggest has been considered and rejected in favor >>>>>> of the current design. Briefly, optionals and throwing errors are >>>>>> distinct because they are considered superior ways for handling distinct >>>>>> types of error. >>>>>> >>>>>> In the case of a simple domain error, there is only one way to fail; >>>>>> therefore, optional return values are considered the best way to model >>>>>> that error. >>>>>> >>>>>> In the case of a recoverable error, the document above describes why >>>>>> marked propagation (the current implementation in Swift) is considered >>>>>> superior to typed propagation (your suggestion). >>>>> >>>>> >>>>> My proposal is not about replacing Optionals and throwing functions with >>>>> a Result type, it’s about separating the representation of an error from >>>>> its propagation. >>>>> Optionals and throwing functions solve two different problems, but they >>>>> are not dealing with two different types of error. >>>> >>>> The basic premise of Swift error handling design is that there exist >>>> different types of error. From the document: >>>> >>>> > What is an error? There may be many different possible error conditions >>>> > in a program, but they can be categorized into several kinds based on >>>> > how programmers should be expected to react to them. Since the >>>> > programmer is expected to react differently, and since the language is >>>> > the tool of the programmer's reaction, it makes sense for each group to >>>> > be treated differently in the language. >>>> >>>>> Optionals are for storing and representing a value that might not exist >>>>> (most commonly due to an unambiguous error). >>>>> Error handling is for propagating an error. >>>>> Returning an optional is essentially the same as returning a non-optional >>>>> and throwing a dedicated “something went wrong” error, because due to the >>>>> optional unwrapping mechanic, you cannot avoid dealing with the fact that >>>>> there might have been an error. Optionals only allow you to delay the >>>>> inevitable error handling, not avoid it. The use cases where the exact >>>>> reason for an error is no important have nothing to do with whether or >>>>> not that error should be available. The optional chaining, if-let >>>>> statements and all other ways one might try to handle an optional value >>>>> do not fundamentally require lack of error information. >>>>> The error handling mechanism, on the other hand, does not concern itself >>>>> with representing the error, but only propagating it. Even an optional >>>>> value with a general-purpose .none case has different levels of >>>>> importance in different cases. More often than not, when propagating an >>>>> optional value to a non-optional target, you’ll be stuck with dealing >>>>> with the error immediately, which is exactly what throwing functions >>>>> force you to do. >>>>> I suggest we enhance the current error representation and propagation >>>>> mechanisms to be able to seamlessly handle cases where an erroneous value >>>>> need to be stored as-is (along with its error) or unpacked and propagated >>>>> (by throwing the error), not just representing a general “error”. >>>>> The general use case is to be able to catch a throwing call into an enum >>>>> that stores the value or the error and then being able to unpack it in a >>>>> throwing context (unpack it or throw the error). >>>>> This use case is a strict superset of the current Optional mechanic >>>>> (catching a potentially missing value) and error handling (always >>>>> manually throwing an error after manual checks). >>>>> The aforementioned suggestion about how to do that is indeed faulty, as >>>>> pointed out by Robert Widmann, but the problem is still valid, in my >>>>> opinion. >>>> >>>> I'd highly recommend taking some time to digest the existing rationale. >>>> You're basing your argument on contradicting the fundamental premise of >>>> the existing design, which begins with this: (a) there are different types >>>> of error; (b) the programmer is expected to react differently to different >>>> types of error; (c) the language is a tool to help the programmer react; >>>> (d) optionals and errors are not unified, and unification is a non-goal, >>>> because they are designed to help the programmer react differently to >>>> different types of error. >>>> >>>> Do you have a specific use case in mind that is not well accommodated by >>>> optionals or by throwing functions? What is it? Into what category does >>>> that use case fall, in terms of the types of error enumerated in the error >>>> handling rationale document? >>>> >>>>>>> On Sun, Apr 30, 2017 at 13:51 Gor Gyolchanyan via swift-evolution >>>>>>> <swift-evolution@swift.org> wrote: >>>>>>> >>>>>>> > On Apr 30, 2017, at 9:29 PM, Robert Widmann >>>>>>> > <devteam.cod...@gmail.com> wrote: >>>>>>> > >>>>>>> > >>>>>>> >> On Apr 30, 2017, at 1:43 PM, Gor Gyolchanyan <g...@gyolchanyan.com> >>>>>>> >> wrote: >>>>>>> >> >>>>>>> >> It doesn’t have to be a massive source-break, since this pitch is >>>>>>> >> supposed to be a strict superset of what Optional and throwing is >>>>>>> >> currently. >>>>>>> >> The only thing that I can think of at this moment that would break >>>>>>> >> is this syntax: >>>>>>> >> >>>>>>> >> let foo: Int? = .none // Error: Can’t convert (Error) -> Int? to Int? >>>>>>> >> >>>>>>> > >>>>>>> > Except it’s not a strict superset if you break every use of this case >>>>>>> > as an RValue. Especially when so much of Swift’s syntax and major >>>>>>> > patterns revolve around the manipulation of optionals. >>>>>>> > >>>>>>> >> The ExpressibleByNilLiteral, the try/throw syntax, all of those >>>>>>> >> things would work as they are right now. >>>>>>> >> Error handling as it is currently, is essentially a hidden `error` >>>>>>> >> out parameter and a whole bunch of codegen. >>>>>>> >> Even the semantical changes described earlier would be purely >>>>>>> >> additive. >>>>>>> > >>>>>>> > Don’t get me wrong, I think you’ve identified the problem space well, >>>>>>> > I just disagree with the solution. >>>>>>> >>>>>>> Yeah, you’re right. It would take some next-level fixits to deal with >>>>>>> the consequences of changing the most fundamental data type of Swift I >>>>>>> can think of. >>>>>>> I’d really appreciate it if you’d offer an alternative solution to this >>>>>>> problem. >>>>>>> The problem, as I understand it, is as follows: >>>>>>> >>>>>>> A lot of Swift’s logic revolves around the notion that some values >>>>>>> might be missing for whatever reason and some functions might fail for >>>>>>> whatever reason. >>>>>>> Any function’s effect can be summed up as the union of its return value >>>>>>> and the global state that it changes (that includes captured closure >>>>>>> scopes). >>>>>>> This could be boiled down to the statement that “Values that a function >>>>>>> sets and returns completely express the purpose of the function”. >>>>>>> The optional gives an extremely convenient way of representing values >>>>>>> that might not exist (which, when returned from a function often means >>>>>>> “failed for an unknown reason”). >>>>>>> The fact that Optional is a type, rather then a function attribute >>>>>>> allows us to store and imperatively manipulate the outcome of logically >>>>>>> failable functions, but unfortunately, it doesn’t allow us to reason >>>>>>> about the cause of the failure. >>>>>>> On the other hand, throwing functions captures the logic of dealing >>>>>>> with specific failures very well, but does not allow us to store and >>>>>>> manipulate them easily, leaving us with workarounds like wrapping >>>>>>> errors in enums with values and re-throwing the errors on their way out >>>>>>> of the generic pipeline. >>>>>>> I’d like to come up with a solution that would unify the optionals and >>>>>>> the throwing functions into a single mechanism for dealing with the >>>>>>> concept of failure, taking the best of both worlds and getting the >>>>>>> benefits of the new synergies. >>>>>>> This pitch was a first rough idea about the direction in which we could >>>>>>> go in trying to find a solution. >>>>>>> I chose to enhance Optional instead of introducing a new type like >>>>>>> Failable, so that we could make do with minimal language changes and >>>>>>> migration procedures. >>>>>>> >>>>>>> This problem is kinda similar to the variadic parameter problem, which >>>>>>> makes it impossible to forward calls to variadic functions simply >>>>>>> because that feature is too magical and does not provide a way to store >>>>>>> and propagate its logic. >>>>>>> >>>>>>> Another way I could think of solving it would be to allow overloading >>>>>>> the postfix `!` and `?` operators (which would currently only be >>>>>>> defined for Optionals), which would allow us to define the Failable >>>>>>> enum type with some error handling syntax integration and make it feel >>>>>>> more at home in the midst of Optionals. >>>>>>> >>>>>>> Or better yet, make an OptionalProtocol and move the current magical >>>>>>> logic to it, leaving the existing Optional perfectly intact and >>>>>>> allowing userspace implementations. >>>>>>> This would also greatly benefit numerous use cases of “invalidatable” >>>>>>> types (like file handlers that can be closed) that would no longer have >>>>>>> to either fatalError or use unwieldy wrappers that operate on Optionals. >>>>>>> >>>>>>> > ~Robert Widmann >>>>>>> > >>>>>>> >> >>>>>>> >>> On Apr 30, 2017, at 8:35 PM, Robert Widmann >>>>>>> >>> <devteam.cod...@gmail.com> wrote: >>>>>>> >>> >>>>>>> >>> This "revamp" is isomorphic to adding a Sum type to stdlib and >>>>>>> >>> plumbing error handling syntax through. I'd much rather see that >>>>>>> >>> than the massive source-break this would entail. >>>>>>> >>> >>>>>>> >>> ~Robert Widmann >>>>>>> >>> >>>>>>> >>> 2017/04/30 13:11、Gor Gyolchanyan via swift-evolution >>>>>>> >>> <swift-evolution@swift.org> のメッセージ: >>>>>>> >>> >>>>>>> >>>> I’d like to suggest a bit of redesigning the Optional type and >>>>>>> >>>> throwing functions to provide a single powerful and flexible >>>>>>> >>>> mechanism for dealing with unexpected situations. >>>>>>> >>>> >>>>>>> >>>> In short, The Optional would have an associated value of type >>>>>>> >>>> Error added to its `none` case, which would describe the reason >>>>>>> >>>> why the wrapped value is missing. >>>>>>> >>>> >>>>>>> >>>> public enum Optional<Wrapped> { >>>>>>> >>>> >>>>>>> >>>> case .some(Wrapped) >>>>>>> >>>> >>>>>>> >>>> case .none(Error) >>>>>>> >>>> >>>>>>> >>>> } >>>>>>> >>>> >>>>>>> >>>> The Optional's ExpressibleByNilLiteral would initialize it with an >>>>>>> >>>> error that corresponds to what is currently fatalError-ed as >>>>>>> >>>> "unexpectedly found nil while unwrapping an Optional value". >>>>>>> >>>> >>>>>>> >>>> The forced unwrapping operator (postfix `!`) would behave the same >>>>>>> >>>> way as it does now, except in case of a fatal error it would print >>>>>>> >>>> out the underlying error, instead of the aforementioned hard-coded >>>>>>> >>>> string. >>>>>>> >>>> >>>>>>> >>>> The optional chaining operator (postfix `?`) would behave the same >>>>>>> >>>> way as it does now, except when it stops evaluating and returns >>>>>>> >>>> the Optional, it would contain the error, returned by the >>>>>>> >>>> sub-expression that failed to evaluate. >>>>>>> >>>> >>>>>>> >>>> Any throwing function would be equivalent to a function that >>>>>>> >>>> returns an Optional. If the function is declared as throwing and >>>>>>> >>>> returning an Optional at the same time, it would be equivalent to >>>>>>> >>>> a function returning an Optional Optional. >>>>>>> >>>> >>>>>>> >>>> The if-let statement would bind the `let` variable to the wrapped >>>>>>> >>>> value inside the "then" block and would bind it to the error in >>>>>>> >>>> the "else" block. Chained else-if blocks would all be considered >>>>>>> >>>> part of the overarching "else" block, so all of them would be able >>>>>>> >>>> to access the error bound to the if-let name. >>>>>>> >>>> >>>>>>> >>>> The guard-let and case-let statements are essentially just >>>>>>> >>>> rewrites of if-let with some added logic. >>>>>>> >>>> >>>>>>> >>>> The `try` keyword, applied to an optional would behave like this: >>>>>>> >>>> >>>>>>> >>>> public func try<T>(_ optional: T?) throws -> T { >>>>>>> >>>> guard let wrapped = optional else { >>>>>>> >>>> throw wrapped // Remember, if-let, guard-let and case-let >>>>>>> >>>> statements bind the let name to the error in case of a failure. >>>>>>> >>>> } >>>>>>> >>>> return wrapped >>>>>>> >>>> } >>>>>>> >>>> >>>>>>> >>>> Multiple let bindings in a single if-let statement are essentially >>>>>>> >>>> rewrites of a nested chain of if-let statements. >>>>>>> >>>> >>>>>>> >>>> The `try` keyword applied to an optional would unwrap the value or >>>>>>> >>>> throw the error. >>>>>>> >>>> The `try?` keyword applied to a throwing function call would cause >>>>>>> >>>> any thrown errors to be caught and put into the returned Optional, >>>>>>> >>>> instead of simply ignored. >>>>>>> >>>> The `try!` keyword applied to a throwing function call would >>>>>>> >>>> behave as you'd expect: just like `try?` except immediately >>>>>>> >>>> force-unwrapped. >>>>>>> >>>> >>>>>>> >>>> A throwing function would be convertible to a non-throwing >>>>>>> >>>> optional-returning function and vice versa. >>>>>>> >>>> This would allow making use of throwing functions when dealing >>>>>>> >>>> with generics or protocols that allow arbitrary return types, >>>>>>> >>>> without having to sacrifice the convenience of error-handling >>>>>>> >>>> logic. Conversely, it would allow to write generic code that deals >>>>>>> >>>> with any type of function without having to implement special >>>>>>> >>>> cases for throwing functions. This means that the two function >>>>>>> >>>> types would be interchangeable and one would be able to satisfy >>>>>>> >>>> protocol requirements of the other. The `rethrows` idiom would >>>>>>> >>>> then become a natural consequence of writing generic functions >>>>>>> >>>> that may return optional and non-optional results just as well. >>>>>>> >>>> >>>>>>> >>>> _______________________________________________ >>>>>>> >>>> swift-evolution mailing list >>>>>>> >>>> swift-evolution@swift.org >>>>>>> >>>> https://lists.swift.org/mailman/listinfo/swift-evolution >>>>>>> >> >>>>>>> > >>>>>>> >>>>>>> _______________________________________________ >>>>>>> swift-evolution mailing list >>>>>>> swift-evolution@swift.org >>>>>>> https://lists.swift.org/mailman/listinfo/swift-evolution >
_______________________________________________ swift-evolution mailing list swift-evolution@swift.org https://lists.swift.org/mailman/listinfo/swift-evolution