Oh look, a Result! Yes, this is a very useful type that should be part of the base language. Both guard / catch and Result would be great for Swift’s error handling.
Jon > On Oct 8, 2017, at 2:01 AM, Tyler Cloutier via swift-evolution > <swift-evolution@swift.org> wrote: > > Also the Scala approach already works quite nicely in Swift 4 in case anyone > was curious: > > enum Try<T> { > case success(T) > case failure(Error) > > init(_ f: @autoclosure () throws -> T) { > do { > self = .success(try f()) > } catch { > self = .failure(error) > } > } > } > > > let handlerDisposableTry = Try(try startHandler(observer)) > > switch handlerDisposableTry { > case .success(let x): > x?.dispose() > case .failure(let error): > print(error) > } > > It’s just a touch awkward with the double try. > > Tyler > > >>> On Oct 7, 2017, at 10:42 PM, Tyler Cloutier <cloutierty...@aol.com> wrote: >>> >>> try startHandler(observer) catch { >>> observer.sendFailed(error) >>> } >> >> >> Technically the above doesn’t make sense. Please disregard. >> >>> On Oct 7, 2017, at 10:35 PM, Tyler Cloutier <cloutierty...@aol.com> wrote: >>> >>> Has there been any progress on this? I came here to propose this but came >>> upon this thread first. >>> >>> This proposal goes way beyond sugar. I find myself constantly in the >>> following situation: >>> >>> let observer = Observer(with: CircuitBreaker(holding: self)) >>> do { >>> let handlerDisposable = try startHandler(observer) >>> } catch { >>> observer.sendFailed(error) >>> } >>> >>> cancelDisposable = ActionDisposable { >>> observer.sendInterrupted() >>> handlerDisposable?.dispose() // Error!!!! handlerDisposable is >>> not defined here >>> } >>> >>> It’s not as simple as putting it all in the do block because then I’ll be >>> catching errors I might not want to catch! (In this case if the initializer >>> of ActionDisposable was capable of throwing.) >>> >>> This is my frustration with every language that has this style of error >>> handling. FWIW, Scala is the only language that I have seen with this style >>> of error handling that solves this problem. The built-in type `Try` >>> combined with pattern matching fixes this issue of over extending the catch >>> area. Which is kinda nifty and also bridges the functional gap as well. >>> >>> object Try { >>> /** Constructs a `Try` using the by-name parameter. This >>> * method will ensure any non-fatal exception is caught and a >>> * `Failure` object is returned. >>> def apply[T](r: => T): Try[T] = >>> try Success(r) catch { >>> case NonFatal(e) => Failure(e) >>> } >>> } >>> } >>> >>> It also would make >>> >>> try startHandler(observer) catch { >>> observer.sendFailed(error) >>> } >>> >>> an obvious extension which to me makes sense since it’s just treating the >>> one function call expression as it’s own implicit do block. >>> >>> Tyler >>> >>> >>> >>>> On Jul 11, 2017, at 10:31 AM, Christopher Kornher via swift-evolution >>>> <swift-evolution@swift.org> wrote: >>>> >>>> >>>> >>>> Begin forwarded message: >>>> >>>> From: Christopher Kornher <ckorn...@me.com> >>>> Subject: Re: [swift-evolution] [Pitch] Guard/Catch >>>> Date: July 10, 2017 at 5:10:15 PM MDT >>>> To: Elviro Rocca <retired.hunter.dj...@gmail.com> >>>> >>>> This messages was modified from the original accidentally sent out >>>> privately, earlier. >>>> >>>> FYI this works today in Xcode 9.0 beta 2 playgrounds: >>>> >>>> ``` >>>> class X { >>>> init() throws {} >>>> >>>> func foo() { >>>> print( "Things succeeded" ) >>>> } >>>> } >>>> >>>> func bar() throws -> X { return try X() } >>>> >>>> >>>> func f() >>>> { >>>> guard let x1:X = try? X(), let x2:X = try? bar() else { >>>> print( "Things failed ") >>>> return >>>> } >>>> >>>> x1.foo() >>>> x2.foo() >>>> } >>>> >>>> f() // works >>>> ``` >>>> >>>> >>>> Most of the examples of this proposed feature don’t handle the exceptions >>>> other than to perform an early return. >>>> So, without handing exceptions, the only unhandled case is a >>>> non-returning throwing function or init: >>>> >>>> ``` >>>> class X { >>>> init() throws {} >>>> >>>> func foo() { >>>> print( "Things succeeded" ) >>>> } >>>> } >>>> >>>> func bar() throws{ let _ = try X() } >>>> >>>> >>>> func f() >>>> { >>>> do { >>>> try bar() >>>> } catch { >>>> return >>>> } >>>> >>>> guard let x:X = try? X() else { >>>> print( "Things failed ") >>>> return >>>> } >>>> >>>> x.foo() >>>> } >>>> >>>> f() // works >>>> ``` >>>> >>>> Having to call a throwing, Void method before performing the rest of a >>>> non-throwing function (or closure ) seems like an edge case to me. Perhaps >>>> I am just biased by my experience. I have not created or used many >>>> throwing initializers and certainly none in guard statements, and if they >>>> were in guard statements, the need to handle exceptions differently from >>>> any other guard failure seems ever more unlikely. >>>> >>>> I don’t think that the small rightward drift of exception handling is >>>> onerous. It is hardly like the “pyramid of doom” that ```guard``` was >>>> created to fix. >>>> >>>> ``` >>>> func f( y:Int? = nil ) >>>> { >>>> do { >>>> try bar() >>>> let x:X = try X() >>>> >>>> guard let y = y else { >>>> print( "No y") >>>> return >>>> } >>>> >>>> x.foo() >>>> print( "y=\(y)") >>>> } catch { >>>> // Handle some exceptions. >>>> return >>>> } >>>> } >>>> ``` >>>> >>>>> On Jul 10, 2017, at 1:45 AM, Elviro Rocca via swift-evolution >>>>> <swift-evolution@swift.org> wrote: >>>> >>>> This is not a sugar proposal, in the same way as "guard" is not syntactic >>>> sugar, because it requires exiting the scope on the else branch, adding >>>> expressive power and safety to the call: also, the sugary part is pretty >>>> important because it avoids nested parentheses and very clearly states >>>> that if the guard condition is not fulfilled, the execution will not reach >>>> the next lines of code. Guard is useful to push the programmer to at least >>>> consider an early return instead of branching code paths, to achieve >>>> better clarity, readability and lower complexity, and I suspect is one of >>>> the best Swift features for many people. >>>> >>>> Also, the case that the proposal aims to cover is not an edge case at all >>>> for a lot of people, including me. Rethrowing an error is something that I >>>> almost never do, and I consider the "umbrella" do/catch at the top of the >>>> call stack an anti-pattern, but I understand that many people like it and >>>> I'm not arguing against it. I am arguing in favor of having options and >>>> not pushing a particular style onto programmers, and for my (and many >>>> people's) style, a guard/catch with forced return is an excellent idea. In >>>> fact you seem to agree on the necessity of some kind of forced-returnish >>>> catch but your elaborations don't seem (to me) much better than the >>>> proposal itself. >>>> >>>> Dave DeLong raised the point of weird behavior in the case of a function >>>> like: >>>> >>>> >>>> func doSomething() throws → Result? { … } >>>> >>>> >>>> In this case, what would the type of x be? >>>> >>>> >>>> guard let x = try doSomething() catch { /// handle and return } >>>> >>>> >>>> Simple, it would be Optional<Result>. I don't find this confusing at all, >>>> and if the idea that just by seeing "guard let" we should expect a >>>> non-Optional is somehow diffused, I think it's better to eradicate it. >>>> >>>> First of all, if I'm returning an optional from a throwing function, it's >>>> probably the case that I want the Optional to be there in the returned >>>> value: the only reason why I would consider doing that is if the semantics >>>> of Optional are pretty meaningful in that case. For example, when parsing >>>> a JSON in which I expect a String or null to be at a certain key: >>>> >>>> >>>> extension String: Error {} >>>> >>>> func parseString(in dict: [String:Any], at key: String) throws -> String? { >>>> guard let x = dict[key] else { throw "No value found at '\(key)' in >>>> \(dict)" } >>>> if let x = x as? String { return x } >>>> if let x = x as? NSNull { return nil } >>>> throw "Value at '\(key)' in \(dict) is not 'string' or 'null" >>>> } >>>> >>>> >>>> Thus, if I'm returning an Optional from a throwing function it means that >>>> I want to clearly distinguish the two cases, so they shouldn't be >>>> collapsed in a single call: >>>> >>>> >>>> guard let x = try doSomething() catch { /// handle and return } >>>> guard let x = x else { /// handle and return } >>>> >>>> >>>> Also, if a function returns something like "Int??", a guard-let (or >>>> if-let) on the returned value of that function will still bind an "Int?", >>>> thus unwrapping only "one level" of optional. If-let and guard-let, as of >>>> today, just unwrap a single optional level, an do not guaranteed at all >>>> that the bound value is not optional. >>>> >>>> To me guard-let (like if-let) is basically sugar for monadic binding for >>>> Optionals, with the additional expressivity granted by the forced return. >>>> I would love to see the same monadic binding structure applied to throwing >>>> functions. >>>> >>>> >>>> >>>> Elviro >>>> >>>> >>>> >>>> >>>> _______________________________________________ >>>> swift-evolution mailing list >>>> swift-evolution@swift.org >>>> https://lists.swift.org/mailman/listinfo/swift-evolution >>>>> Il giorno 09 lug 2017, alle ore 01:16, Christopher Kornher via >>>>> swift-evolution <swift-evolution@swift.org> ha scritto: >>>>> >>>>> Thanks for you considerate reply. My concern over the proliferation of >>>>> “sugar proposals” is a general one. This proposal has more merit and >>>>> general utiliity than many others. I have never used a throwing function >>>>> in a guard statement that was not itself in a throwing function, but I >>>>> can see that it could possibly be common in some code. Wrapping a guard >>>>> statement and all the code that uses variables set in the guard in a >>>>> do/catch is sub-optimal. >>>>> >>>>> >>>>> All catches don’t have to exit the outer scope, so using guard only >>>>> handles a subset >>>>> >>>>> It think that creating the terse try/catch for simple cases has multiple >>>>> advantages: >>>>> >>>>> 1) I think that it addresses your desire for a simple way to use >>>>> throwing functions easily in guard statements. >>>>> >>>>> 2) It avoids having to change the guard syntax to accomplish this >>>>> >>>>> 3) It is useful for handling simple one line try/catch constructs in >>>>> less space in a way that should not seem too foreign to Swift developers. >>>>> >>>>> 4) It simplifies code that currently uses nested do/try/catch >>>>> constructs. Even though this is rare, it introduces significant >>>>> “rightward drift”. >>>>> >>>>> 5) It can used to return early from void throwing functions easily. >>>>> e.g. : >>>>> >>>>> ``` >>>>> ``` >>>>> >>>>> Multiple void throwing functions would probably be better handled by a >>>>> do/catch block, but there is no danger of needing values from these >>>>> functions because there are none: >>>>> >>>>> ``` >>>>> ``` >>>>> >>>>> I did not think of this before, but perhaps we could allow `do` to be >>>>> replaced with `guard`, thereby allowing values to escape to the outer >>>>> scope, while still ensuring an early exit: >>>>> >>>>> ``` >>>>> ``` >>>>> I am not sure that “leaky” braces are a good idea, so perhaps some other >>>>> character could be used to indicate a non-scope or whatever you want to >>>>> call it: >>>>> >>>>> ``` >>>>> ``` >>>>> This would make the language even harder to read, so just using braces is >>>>> probably a better idea. >>>>> >>>>> This would change the guard syntax slightly, but is a straightforward >>>>> extrapolation of do/catch and guard, I think. Of course, this could >>>>> replace the existing guard syntax entirely and its use of semicolons, if >>>>> we want to go that far… >>>>> >>>>> Allowing this syntax only if one of the expressions throws is possibly a >>>>> good backward-compatible solution that would avoid redundant guard >>>>> syntaxes. >>>>> >>>>> Anyway there are lot of possibilities here. We are not forced to extend >>>>> the guard statement as it exists today. The current guard statement >>>>> syntax was quite controversial when it was introduced and extending it >>>>> may not be the best option to do what you want. >>>>> >>>>> - Chris >>>>> >>>>> >>>>> >>>>> >>>>> >>>>> _______________________________________________ >>>>> swift-evolution mailing list >>>>> swift-evolution@swift.org >>>>> https://lists.swift.org/mailman/listinfo/swift-evolution >>>>>> On Jul 8, 2017, at 4:16 PM, Benjamin Spratling via swift-evolution >>>>>> <swift-evolution@swift.org> wrote: >>>>>> >>>>>> >>>>>> >>>>>> I’ve read your email, but haven’t digested it fully. One thing I agree >>>>>> with is that most functions which call throwing functions don’t actually >>>>>> use a do…catch block, but instead are merely marked “throws” and the >>>>>> error is propagated back through the stack. Once I seriously started >>>>>> coding functions with errors, I realized I almost always wanted my >>>>>> errors to reach my view-controller or my business logic so I could >>>>>> present separate UI if a real error occurred, and often my error message >>>>>> depended on the details of the error instance. >>>>>> >>>>>> >>>>>> >>>>>> I disagree with your conclusion on this point. >>>>>> The “guard” syntax is specifically designed to achieve early return (and >>>>>> placing code associated with early return at the point where it happens) >>>>>> and cleanly installing the returned value into the surrounding scope. >>>>>> So far it has been used to achieve early return only with optionals, >>>>>> true. But is that inherent to ‘guard’, or is it merely because that’s >>>>>> the only way it has been used? The guard does set variables that are >>>>>> needed in the body of the function, and that’s exactly why using guard >>>>>> with values returned from throwing functions makes so much sense, >>>>>> because it does exactly the same thing in a general sense. The >>>>>> “do”…”catch” structure is intentionally designed differently, to place >>>>>> the “happy path” in one place and place the returns in another place. I >>>>>> think with guard/else, we’re seeing developers who can handle less >>>>>> cognitive loading find it easier to reason about early return than >>>>>> grouping failures after the happy path. This proposal hopes to >>>>>> introduce that better language architecture to the catching of errors. >>>>>>> On Jul 8, 2017, at 4:08 PM, Christopher Kornher <ckorn...@me.com> wrote: >>>>>>> >>>>>>> I am opposed to this proposal because it muddies up the language to >>>>>>> support what is essentially an edge case. The standard way to exit a >>>>>>> function early because an exception is thrown is to make the function >>>>>>> itself throw, if it is part of a larger operation. The addition of a >>>>>>> few lines of try/catch code is not a great burden and makes the >>>>>>> termination of an an exception very clear. >>>>>>> `guard` statements are generally used to set variables that are needed >>>>>>> in the body of a function. Using them to save a few lines of exception >>>>>>> handing code is a very different use. There is no need to mix two >>>>>>> relatively clean syntaxes for a few edge cases and increase cognitive >>>>>>> load one more time, >>>>>> guard try foo( ) catch { return } >>>>>> do { >>>>>> try fn1() >>>>>> try fn2() >>>>>> } catch { >>>>>> // keep going, return or call a non-returning function, since >>>>>> throw is already handled by declaring a throwing enclosing function. >>>>>> // No varibles are needed by the outer block because none are >>>>>> set >>>>>> // So it is not clearly a guard-like statement >>>>>> } >>>>>> guard { >>>>>> try fn1() >>>>>> try fn2() >>>>>> let x = fn3() >>>>>> } catch { >>>>>> // Must exit >>>>>> } else { >>>>>> // Must exit >>>>>> } >>>>>> guard <your favorite character here> >>>>>> try fn1() >>>>>> try fn2() >>>>>> let x = fn3() >>>>>> <another favorite character here> catch { >>>>>> // Must exit >>>>>> } else { >>>>>> // Must exit >>>>>> } >>>>>> >>>>>> -Ben Spratling >>>>>> >>>>>> _______________________________________________ >>>>>> 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
_______________________________________________ swift-evolution mailing list swift-evolution@swift.org https://lists.swift.org/mailman/listinfo/swift-evolution