Swift should support generics as arguments to closures in any context


(Spencer Kohan) #1

New here, hope I'm doing this right.

I recently found a use case which does not appear to be covered by the
swift language: it appears that there's no way to declare a closure type
with generic arguments.

For example, what if I'm building an iOS app, and I want to define a
closure which operates on UIViewControllers which implement a particular
protocol?

In ObjectiveC, this would be possible with the following syntax:

    typedef void (^MyBlockType)(UIViewController<MyProtocol>)

In Swift you can do this in the context of a function, class or struct:

    func myFunction<T: UIViewController where T:MyProtocol>() {
        let x : (T) -> () = { generic in
           ....
        }
    }

But what if you want to declare such a closure as a variable within the
protocol it's referring to?

    protocol MyProtocol {
        var protocolControllerCallback : (protocolController : ??? )->()
    }

It seems like Associated Types are the intended tool for this job, so you
could have something like:

    protocol MyProtocol {
        typealias ProtocolObservingController
        var protocolControllerCallback : (protocolController
: ProtocolObservingController )->() {get set}
    }

    class MyController : UIViewController, MyProtocol {
        typealias ProtocolObservingController = MyController
        ...
    }

But this doesn't quite solve the problem: we still can't have a closure
defined in this protocol which can operate on *any* implementations of the
protocol. For instance, what if we wanted to pass a closure between two
different implementations?

    class MyChildController : UIViewController, MyProtocol {
        typealias ProtocolObservingController = MyChildController
        ...
    }

    class MyParentController : UIViewController, MyProtocol {
        typealias ProtocolObservingController = MyParentController
        ...

        func createChildController() -> MyControllerChild {
            let child = MyChildController()
            child.protocolControllerCallback =
self.protocolControllerCallback
        }

    }

It doesn't work because the types of protocolControllerCallback are
inconsistent between the two classes.

This feels like a hole in the language.


(Félix Cloutier) #2

Are you sure that your problem is about generics on lambdas? Seems to me that you wouldn't need generic lambdas if you could solve your initial problem in a different way.

You can use Self in protocols, which refers to the implementing type:

protocol MyProtocol {
  var protocolControllerCallback: Self -> Void { get }
}

class MyController: MyProtocol {
  var protocolControllerCallback: MyController -> Void
  
  init() {
    protocolControllerCallback = { cnt in return }
  }
}

That doesn't work right now because Swift will complain that the class must be `final` for reasons that sound bogus to me: "it uses 'Self' in a non-parameter, non-result type position". I don't know if there's a technical reason that it can't be in a closure parameter. Still seems to get you closer to what you want.

Félix

···

Le 15 janv. 2016 à 05:07:06, Spencer Kohan via swift-evolution <swift-evolution@swift.org> a écrit :

New here, hope I'm doing this right.

I recently found a use case which does not appear to be covered by the swift language: it appears that there's no way to declare a closure type with generic arguments.

For example, what if I'm building an iOS app, and I want to define a closure which operates on UIViewControllers which implement a particular protocol?

In ObjectiveC, this would be possible with the following syntax:

    typedef void (^MyBlockType)(UIViewController<MyProtocol>)

In Swift you can do this in the context of a function, class or struct:

    func myFunction<T: UIViewController where T:MyProtocol>() {
        let x : (T) -> () = { generic in
           ....
        }
    }

But what if you want to declare such a closure as a variable within the protocol it's referring to?

    protocol MyProtocol {
        var protocolControllerCallback : (protocolController : ??? )->()
    }

It seems like Associated Types are the intended tool for this job, so you could have something like:

    protocol MyProtocol {
        typealias ProtocolObservingController
        var protocolControllerCallback : (protocolController : ProtocolObservingController )->() {get set}
    }

    class MyController : UIViewController, MyProtocol {
        typealias ProtocolObservingController = MyController
        ...
    }

But this doesn't quite solve the problem: we still can't have a closure defined in this protocol which can operate on *any* implementations of the protocol. For instance, what if we wanted to pass a closure between two different implementations?

    class MyChildController : UIViewController, MyProtocol {
        typealias ProtocolObservingController = MyChildController
        ...
    }

    class MyParentController : UIViewController, MyProtocol {
        typealias ProtocolObservingController = MyParentController
        ...

        func createChildController() -> MyControllerChild {
            let child = MyChildController()
            child.protocolControllerCallback = self.protocolControllerCallback
        }

    }

It doesn't work because the types of protocolControllerCallback are inconsistent between the two classes.

This feels like a hole in the language.

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Spencer Kohan) #3

If I understand correctly, this still doesn't solve the problem exactly.
Wouldn't Self in this context refer to the class that implements MyProtocol?

The example I raised is just a single use-case I ran into, but I think the
more general point is that there isn't a good way to tell the compiler that
a closure parameter must belong to more than one protocol, or a type plus a
protocol.

Here's another (somewhat contrived) example:

Say I was writing an application like IMDB, and I had a protocol for
directors and another for writers:

    protocol Director {
        func directingCredits() -> [Film]
    }

    protocol Writer {
        func writingCredits() -> [Film]
    }

And I want to have a closure which returns all the movies written and
directed by the same person:

    var getWrittenAndDirectedFilms : (<Writer, Director>)->[Film] = {
writerDirector in
        return writerDirector.writingCredits().filter {
writerDirector.directingCredits().contains($0) }
    }

Not all writers are directors, and not all directors are writers, but it's
perfectly reasonable to have an operation which only works on objects which
conform to those two protocols.

It's easy to write a plain function that handles this use case using
generics, but it's not possible with a closure. It seems like an
unnecessary limitation.

···

On Fri, Jan 15, 2016 at 6:01 PM, Félix Cloutier <felixcca@yahoo.ca> wrote:

Are you sure that your problem is about generics on lambdas? Seems to me
that you wouldn't need generic lambdas if you could solve your initial
problem in a different way.

You can use Self in protocols, which refers to the implementing type:

protocol MyProtocol {
var protocolControllerCallback: Self -> Void { get }
}

class MyController: MyProtocol {
var protocolControllerCallback: MyController -> Void
init() {
protocolControllerCallback = { cnt in return }
}
}

That doesn't work right now because Swift will complain that the class
must be `final` for reasons that sound bogus to me: "it uses 'Self' in a
non-parameter, non-result type position". I don't know if there's a
technical reason that it can't be in a closure parameter. Still seems to
get you closer to what you want.

Félix

Le 15 janv. 2016 à 05:07:06, Spencer Kohan via swift-evolution < > swift-evolution@swift.org> a écrit :

New here, hope I'm doing this right.

I recently found a use case which does not appear to be covered by the
swift language: it appears that there's no way to declare a closure type
with generic arguments.

For example, what if I'm building an iOS app, and I want to define a
closure which operates on UIViewControllers which implement a particular
protocol?

In ObjectiveC, this would be possible with the following syntax:

    typedef void (^MyBlockType)(UIViewController<MyProtocol>)

In Swift you can do this in the context of a function, class or struct:

    func myFunction<T: UIViewController where T:MyProtocol>() {
        let x : (T) -> () = { generic in
           ....
        }
    }

But what if you want to declare such a closure as a variable within the
protocol it's referring to?

    protocol MyProtocol {
        var protocolControllerCallback : (protocolController : ??? )->()
    }

It seems like Associated Types are the intended tool for this job, so you
could have something like:

    protocol MyProtocol {
        typealias ProtocolObservingController
        var protocolControllerCallback : (protocolController
: ProtocolObservingController )->() {get set}
    }

    class MyController : UIViewController, MyProtocol {
        typealias ProtocolObservingController = MyController
        ...
    }

But this doesn't quite solve the problem: we still can't have a closure
defined in this protocol which can operate on *any* implementations of the
protocol. For instance, what if we wanted to pass a closure between two
different implementations?

    class MyChildController : UIViewController, MyProtocol {
        typealias ProtocolObservingController = MyChildController
        ...
    }

    class MyParentController : UIViewController, MyProtocol {
        typealias ProtocolObservingController = MyParentController
        ...

        func createChildController() -> MyControllerChild {
            let child = MyChildController()
            child.protocolControllerCallback =
self.protocolControllerCallback
        }

    }

It doesn't work because the types of protocolControllerCallback are
inconsistent between the two classes.

This feels like a hole in the language.

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

--
Spencer Kohan
cell: 216 288 8258


(Brent Royal-Gordon) #4

there isn't a good way to tell the compiler that a closure parameter must belong to more than one protocol, or a type plus a protocol.

There is a way to do two protocols:

  protocol A {}
  protocol B {}
  let closure: (protocol<A, B> -> Void) = { _ in }

I think this should probably be extended to allow one of the listed protocols to be a class. (There's no need to support structs or enums, since you know their conformances.)

···

--
Brent Royal-Gordon
Architechies