[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