[swift-evolution] [Proposal Idea] catching functions for composable and cps error handling
Matthew Johnson
musical.matthew at mac.com
Fri Dec 18 10:52:54 CST 2015
I’m bumping this post in case you missed it Brent. I would find any comments you have on the differences between our approaches very interesting. Thanks!
> On Dec 17, 2015, at 10:36 AM, Matthew Johnson via swift-evolution <swift-evolution at swift.org> wrote:
>
> Brent, thanks for sharing this. It’s interesting that we independently came up with such similar approaches. That seems to indicate that it is definitely worthy of consideration.
>
> IMO your proposal is actually three proposals. I obviously think “catching functions” might have a chance for consideration right now. I also think @once is a very good idea and probably has a good chance for consideration as an independent proposal. Obviously async is going to have to wait.
>
> I’m going to comment on differences between our approaches to catching functions and include the rationale for my decisions. I hope you will do the same and maybe we can reach a consensus on the best approach.
>
>> A function type can be marked as `catching`.
>>
>> catching T -> U
>
> I prefer the T -> U catches syntax as it more closely matches the syntax for throws, but the syntax is the least important part of this to me and I would be happy with anything sensible.
>
>> Catching functions must be exhaustive (i.e. must include a plain `catch` block), with two exceptions to be described later.
>
> Rather than requiring this I introduced an @exhaustive attribute that can be specified when required, as well as the ability of the caller to find out whether the error was handled or not. I did this partly because of the comparability use case and partly because it affords more flexibility without losing the exhaustive behavior when that is necessary.
>
>> Catch blocks have the same return type as the regular block of their function. For instance, the catch blocks of a `catching Void -> Int` must return `Int`s. If the function is marked `throws`, then the catch blocks can `throw`.
>>
>> func convertOnlyCloudErrors() throws -> String {
>> return “OK”
>> }
>> catch let error as CKErrorCode {
>> return error.description
>> }
>> catch {
>> throw error
>> }
>
> Did you intend to mark this function as `catching`? I’ll assume so as it includes top level `catch` clauses.
>
> This approach is reasonable, but I’m interested in hearing what you think of the other alternative I explored which goes hand-in-hand with non-exhaustive catching functions.
>
>
>> Here’s the first exception to catch exhaustiveness: a `catching throws` function has an implicit `catch { throw error }` block added if necessary. So that second catch block in the previous example is redundant.
>
> I don’t like this. I think it is better to keep things explicit. It would also preclude non-exhaustive `catching` functions which I think have interesting use cases.
>
>>
>> To call the function normally, just…call it normally.
>>
>> foo()
>> cloudKitQueryOp.queryCompletionBlock!(cursor)
>>
>> To send an error to it, use one of these:
>>
>> foo(throw error)
>> foo(try someOperationThatMightFail()) // unconditionally calls foo() with the result, whether error or datum
>> foo(try? someOperationThatMightFail()) // if an error is thrown, calls foo with the error and returns nil; if not, returns the result of the operation without calling foo
>>
>> I’m not totally satisfied with this syntax, and we should probably try to come up with something better. BUT NOT RIGHT NOW. Bikeshedding can wait.
>
> I don’t mean to bikeshed but I do think this particular syntax has serious problems:
>
> func bar() throws {
> // in both cases, should the error be thrown or caught by foo?
> foo(throw error)
> foo(try someOperationThatMightFail())
> }
>
> It might be possible to define this problem away, but even then you would not know the answer without knowing the signature of `foo`. That is really bad for readability.
>
> What do you think of the syntax I used, which is similar to yours while avoiding these issues?
>
> foo(catch error)
>
>
>> One issue with this is the question of whether a `throw` or non-optional `try` with a catching function implicitly `return`s immediately after calling the catching function. My current thinking is that it does *not* cause an immediate return, so you can come up with a return value yourself. But this is not entirely satisfying.
>
> This is only a problem because your syntax overloaded the existing throw and try keywords and because of that introduced semantic confusion.
>
>> To Objective-C, a catching function is a block with the same parameters, only nullable, followed by a nullable NSError. If there are no parameters, then an initial BOOL parameter is inserted.
>>
>> @property (nonatomic, copy, nullable) void (^queryCompletionBlock)(CKQueryCursor * __nullable cursor, NSError * __nullable operationError);
>> var queryCompletionBlock: (catching CKQueryCursor -> Void)?
>
> Glad to see you address Objective-C interop. I hadn’t considered that yet.
>
> Matthew
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution
More information about the swift-evolution
mailing list