2

I have a case where I want to register either one argument or no argument closures with a service. There's always an argument available, but for brevity, I want to be able to register no arg closures as well, and then just dispatch the closure without the available argument in that case. Coming from a strong OO and dynamic types background where we love polymorphic dispatch and class inheritance trees and let the types figure themselves out, I can throw the following together:

class AbstractAction<T> {
    func publish(value:T) {
        fatalError("you should override this")
    }
}

class NullaryAction<T>: AbstractAction<T> {
    var closure:() -> ()
    override func publish(_:T) {
        closure()
    }
    init(closure:()->()) {
        self.closure = closure
    }
}

class UnaryAction<T>: AbstractAction<T> {
    var closure:(T) -> ()
    override func publish(value:T) {
        closure(value)
    }
    init(closure:(T)->()) {
        self.closure = closure
    }
}

var action:AbstractAction = UnaryAction<Int>(closure: { print("\($0)") })
action.publish(42)
action = NullaryAction<Int>(closure: { print("something happened") } )
action.publish(42)

So I see 42 followed by something happened in my console. Great.

But I'd like to explore doing this with struct and/or enum. Value semantics are all the rage. The enum approach was relatively straightforward, I think:

enum Action<T> {
    case Nullary( ()->() )
    case Unary( (T)->() )

    func publish(value:T) {
        switch self {
        case .Nullary(let closure):
            closure()
        case .Unary(let closure):
            closure(value)
        }
    }
}

var action = Action.Unary({ (arg:Int) -> () in print("\(arg)") })
action.publish(42)
action = Action<Int>.Unary( { print("shorthand too \($0)") } )
action.publish(42)
action = Action<Int>.Nullary({ print("something happened") })
action.publish(42)

To do a struct approach, I it is my understanding that I should use a protocol to capture common interface of publish(value:T). But that's where things get confusing, because protocols apparently can't be mixed with generics? I tried:

struct NullaryAction<T> {
    typealias ValueType = T
    var closure:() -> ()
}

struct UnaryAction<T> {
    typealias ValueType = T
    var closure:(T) -> ()
}

protocol Action {
    typealias ValueType
    func publish(value:ValueType)
}

extension NullaryAction: Action {
    func publish(_:ValueType) {
        self.closure()
    }
}

extension UnaryAction: Action {
    func publish(value:ValueType) {
        self.closure(value)
    }
}

var action:Action = UnaryAction(closure: { (arg:Int) -> () in print("\(arg)") })
action.publish(42)
action = UnaryAction<Int>(closure: { print("shorthand too \($0)") } )
action.publish(42)
action = NullaryAction<Int>(closure:{ print("something happened") })
action.publish(42)

This just produces a lot of errors at the bottom. I had tried to do the extensions as generics (e.g. extension NullaryAction<T>:Action), but it told me that T was unused, even though I had placed the typealias expressions in the extensions.

Is it possible to do this with struct/protocol? I'm happy with the enum solution, but was disappointed I couldn't realize it with the struct/protocol approach.

Travis Griggs
  • 21,522
  • 19
  • 91
  • 167
  • Protocols and generics can be quite tricky in Swift. Maybe the answer in this thread can help you: http://milen.me/writings/swift-generic-protocols/ (see also forums.developer.apple.com/thread/7350). Both these are however relating having a generic return type of some protocol function, but maybe you can work your dynamic type magic to work something out :) I'm interested to know the outcome, for your specific example (tried to fix something but failed rather miserably...) – dfrib Dec 11 '15 at 17:40

1 Answers1

0

Judging by the fact that you want to cast your structs to their protocols (by using var action: Action = UnaryAction {...}), I'm assuming you don't need the publish method to have the right signature upon evoking it.

In other words, by declaring your Action protocol with a typealias, the compiler is expecting to specialise the publish method for each instance of your structs.

This means that you have two options:

  1. Remove the type casting:

Example:

var action /*removed the :Action type casting */ = UnaryAction<Int>(closure: { (arg:Int) -> () in print("\(arg)") })
action.publish(42)
action = UnaryAction<Int>(closure: { print("shorthand too \($0)") } )
action.publish(42)
var anotherAction = NullaryAction<Int>(closure:{ print("something happened") }) //need to use another variable for this last one
anotherAction.publish(42)

This solution makes your publish methods also have the same signature that your structs have. If your struct is specialised to work with Ints, then you'll have .publish(value: Int).

  1. Make the protocol non-generic

Example:

protocol Action {
    func publish(value:Any)
}

struct NullaryAction<T>: Action {
    let closure: () -> ()
    init(closure: () -> ()) {
        self.closure = closure
    }
    func publish(value:Any) {
        self.closure()
    }
}

struct UnaryAction<T>: Action {
    let closure: (T) -> ()
    init(closure: (T) -> ()) {
        self.closure = closure
    }
    func publish(value:Any) {
        self.closure(value as! T) //Need to type cast here
    }
}

var action: Action = UnaryAction<Int>(closure: { (arg:Int) -> () in print("\(arg)") })
action.publish(42)
action = UnaryAction<Int>(closure: { print("shorthand too \($0)") } )
action.publish(42)
action = NullaryAction<Int>(closure:{ print("something happened") })
action.publish(42)

This solution allows you to keep on type casting, but your publish methods will all have the same signature (.publish(value: Any)). You also need to account for that upon executing the closure.

Bell App Lab
  • 818
  • 6
  • 14