How to Create Generic Closures in Swift

is it possible to create a generic closure in Swift?

No, because variables and expressions can't be generic. There are only generic functions and generic types.


To clarify: In some languages you can have types with a universal quantifier, like forall a. a -> a. But in Swift, types cannot have a universal quantifier. So expressions and values cannot be themselves generic. Function declarations and type declarations can be generic, but when you use such a generic function or an instance of such a generic type, some type (which could be a real type or a type variable) is chosen as the type argument, and thereafter the value you get is no longer itself generic.

How to create generic closures in Swift

You cannot pass a closure of type String->Void to a parameter of type AnyObject->Void.

However, you can define a generic function:

func saveWithCompletionObject<T>(obj : T, success : T -> Void, failure : Void -> Void) {
// ...
success(obj)
}

Now the compiler can verify that obj has the same type
as the parameter of success, for example:

func doSomething(success : String -> Void, failure : Void -> Void)
{
saveWithCompletionObject("Example", success, failure)
}

func doSomethingElse(success : Int -> Void, failure : Void -> Void)
{
saveWithCompletionObject(13, success, failure)
}

But I would recommend that saveWithCompletionObject just takes a Void->Void
parameter (without generics):

func saveWithCompletionObject(success : Void -> Void, failure : Void -> Void) {
// ...
success()
}

and the caller wraps its closure:

func doSomething(success : String -> Void, failure : Void -> Void)
{
saveWithCompletionObject( { success("Example") } , failure)
}

func doSomethingElse(success : Int -> Void, failure : Void -> Void)
{
saveWithCompletionObject( { success(13) }, failure)
}

This is more flexible, e.g. for callback functions with more than one
parameter:

func andNowForSomethingCompletelyDifferent(success : (Int, Double) -> Void, failure : Void -> Void)
{
saveWithCompletionObject( { success(13, M_PI) }, failure)
}

iOS Generic swift closure

You can create your method something like:

func method1<Model: Codable>(onCompletion: @escaping (Model?, Error?) -> Void) -> Void {
let decoder = JSONDecoder()
let itemObj = try? decoder.decode(Model.self, from: data)
onCompletion(itemObj, nil)
}

And if you have your struct like:

struct ModelObject: Codable {
}

you can call this method like:

method1 { (model: ModelObject?, error) in

}

You have another struct like:

struct AnotherModelObject: Codable {
}

You can call it like:

method1 { (model: AnotherModelObject?, error) in

}

Swift generic closure

You can make the Cell class generic, e.g.

class Cell<T : UITableViewCell> {
}

and then use T instead of every UITableViewCell.

Unfortunately you would have to have the same in both Section and Form classes, too. That would work for tables with one type of cells but it won't probably work for tables with multiple cell types. In that case you will always need casting somewhere.

Closure with generic parameters

I believe what you're asking for can't make sense (having nothing to do with Swift). While I'm interested in being proven wrong, I don't believe this could be reasonably created in any strongly typed language. (EDIT: continuing my research, I believe this would be possible in a language with first-class polymorphism, but I am not aware of any general-use languages that actually have this feature.)

let myClosure = {<S where S: MyProtocol, S: MySuperClass>(param: S) in ... }

What type would you expect myClosure to be? A generic creates an abstract type. It does not become a real type until it is specialized. So myClosure would be of an abstract type itself. That's like asking for an instance of an abstract class. The whole point of "abstract" is you can't construct one. The best you could say would be that myClosure would itself be a type that you would need to instantiate into a real instance (but then let doesn't make any sense; you don't let types).

When you wrap this in a struct, what you're really doing is creating an abstract type that you will specialize into a real type when you create an instance.

Now what would make sense IMO (but appears currently to be impossible), is this:

typealias Mapping<S> = S -> S
let identity: Mapping<Int> = { return $0 }

That makes sense because you're defining an abstract type (Mapping), but then instantiating a concrete type Mapping<Int>. Unfortunately, typealias does not appear to support generics at this point, so a struct is probably the best tool we have.


Note that while typealias is a bust, it is clearly possible to specialize function variables themselves. This isn't a closure, I know, but may be useful in some of the same situations.

func Identity<T>(i:T) -> T {
return i
}

let identityInt:(Int -> Int) = Identity
identityInt(1) // => 1

Using this to explore the problem of abstract types a little more, consider:

func Identity<T>(i:T) -> T { return i }
let x = Identity

This fails to compile with the error:

error: cannot convert the expression's type '(T) -> T' to type '(T) -> T'

That's because the type (T) -> T is not a concrete type, so you can't have one called x. Compare that to identityInt, which I explicitly specialized into a concrete type, and then could construct.

Swift generic closures sum

You are running into the issue of Swift generic coercion misunderstanding. Generic types in Swift are invariant, which means that Styler<A> and Styler<B> are completely unrelated types even if A and B are related (subclasses for instance).

This is why Style<UILabel> and Styler<UIView> are unrelated. However, closures (and hence functions) are variant (as explained here) - covariant on the return type, and contravariant on the parameter types, this is why your first example works.

Because of this, you can pass a UILabel to a Styler<UIView>.apply, since that is a simple function call, which accepts subclasses of the declared input argument type.

let styler1: Styler<UIView> = Styler { (label: UIView) -> Void in
label.backgroundColor = UIColor.red
}

let styler2: Styler<UIView> = Styler { (view: UIView) -> Void in
view.backgroundColor = UIColor.green
}

let styler3 = styler1 + styler2

styler1.apply(UILabel())


Related Topics



Leave a reply



Submit