Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I "generify" a closure type alias in Swift?

In order to make my code easier to read, I am using type aliases in Swift for various types of closures. I have the following basic set of closures:

public typealias FailureClosure = (error: NSError?) -> Void
public typealias ProgressClosure = (progress: Float32) -> Void
public typealias BasicClosure = () -> Void

I would like to add a closure typealias that supports generic arrays, but I can't seem to figure out the syntax for it. This is as far as I am able to get, but I get the compile time error "Use of undeclared type 'T'"

public typealias ArrayClosure = <T>(array:[T]?) -> Void

Does anybody know how to do this? Or even if it is possible?

like image 736
Michael Gaylord Avatar asked Sep 18 '14 10:09

Michael Gaylord


2 Answers

No, this is not currently possible. If it were possible, the syntax you'd expect would be:

public typealias ArrayClosure<T> = (array:[T]?) -> Void

You would then use it like ArrayClosure<Int>. But it's not currently legal.

That said, I don't really recommend these type aliases. They obscure more than they illuminate. Compare this signature:

func foo(onError: FailureClosure)

with:

func foo(onError: NSError? -> Void)

To save just a couple of characters, you force the caller to guess what FailureClosure passes. The error or progress tags don't really help you (you still need to use progress in ...).

The one case that makes a lot of sense is around progress, but I think the type you want there is:

public typealias Progress = Float32

Don't get me wrong here, type aliasing can be very helpful when it creates a new type. Progress is a type that happens to be implemented as as float. But much of what you're doing (definitely with ArrayClosure, and to a lesser extent with the others) is just creating new syntax without creating a new type, and that is more often confusing than helpful.


To call out your specific example, and why overuse of type aliases can cause you to overcomplicate your design:

func foo(failure: ((error: NSError?) -> ())? = nil)

You're right that this is really complicated. Compare:

func foo(failure: NSError -> Void = {_ in return})

Two big changes here. There's no reason to have a failure block that takes an optional error. Always pass an error (if there's no error, why would failure be called?). And there's no reason for the failure block to be optional. If you really want a default value, just make the default value do nothing. Two optionals gone, and all the consuming and implementing code gets simpler. Always think carefully about whether something absolutely must be optional. Optionals add a lot of complexity; don't add them lightly.

Personally, I'd probably do this with an overload instead in many cases:

func foo(#failure: NSError -> Void) { ...  }

func foo() {
  foo(failure:{ _ in return })
}

I just think it's a little easier to understand what's going on. But either way is fine.


EDIT (Dec 2014): After writing Swift for a few more months, I've grown more fond of @David's approach in the comments below, which is to use an optional for the closure, but not for the error. Particularly given Swift's optional chaining syntax (failure?()), it often does wind up being clearer.

func foo(failure: (NSError -> Void)? = nil)
like image 152
Rob Napier Avatar answered Oct 16 '22 04:10

Rob Napier


Swift 4.1 supports generic type aliases. You can use this feature to provide a name for function with generic parameters.

You may have to use such declaration:

public typealias ArrayClosure<T> = ([T]?) -> Void
like image 33
Roman Podymov Avatar answered Oct 16 '22 06:10

Roman Podymov