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 <[email protected]> wrote:
> 
> On Mon, May 1, 2017 at 2:58 AM, Gor Gyolchanyan <[email protected] 
> <mailto:[email protected]>> 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 <[email protected] 
>> <mailto:[email protected]>> wrote:
>> 
>> On Sun, Apr 30, 2017 at 5:05 PM, Gor Gyolchanyan <[email protected] 
>> <mailto:[email protected]>> wrote:
>> 
>>> On May 1, 2017, at 12:10 AM, Xiaodi Wu <[email protected] 
>>> <mailto:[email protected]>> wrote:
>>> 
>>> You may wish to read the rationale behind the current error handling design:
>>> 
>>> https://github.com/apple/swift/blob/master/docs/ErrorHandlingRationale.rst 
>>> <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 
>>> <[email protected] <mailto:[email protected]>> wrote:
>>> 
>>> > On Apr 30, 2017, at 9:29 PM, Robert Widmann <[email protected] 
>>> > <mailto:[email protected]>> wrote:
>>> >
>>> >
>>> >> On Apr 30, 2017, at 1:43 PM, Gor Gyolchanyan <[email protected] 
>>> >> <mailto:[email protected]>> 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 <[email protected] 
>>> >>> <mailto:[email protected]>> 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 
>>> >>> <[email protected] <mailto:[email protected]>> のメッセージ:
>>> >>>
>>> >>>> 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
>>> >>>> [email protected] <mailto:[email protected]>
>>> >>>> https://lists.swift.org/mailman/listinfo/swift-evolution 
>>> >>>> <https://lists.swift.org/mailman/listinfo/swift-evolution>
>>> >>
>>> >
>>> 
>>> _______________________________________________
>>> swift-evolution mailing list
>>> [email protected] <mailto:[email protected]>
>>> https://lists.swift.org/mailman/listinfo/swift-evolution 
>>> <https://lists.swift.org/mailman/listinfo/swift-evolution>
_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to