Generic implementation doesn't work with concrete types

Yeah, double dispatch is what I'm trying to achieve but so far without much luck to make it generic. Here's an example:

struct State {
    let value: Int
}

struct OtherState {
    let value: Int
}

struct SuperState {
    let state1: State
    let state2: OtherState
}

struct Add {
    let value: Int

    func dispatch(_ state: SuperState) -> SuperState {
        SuperState(
            state1: self.dispatch(state.state1),
            state2: self.dispatch(state.state2)
        )
    }

    func dispatch(_ subject: State) -> State {
        State(
            value: subject.value + value
        )
    }

    func dispatch(_ subject: OtherState) -> OtherState {
        OtherState(
            value: subject.value + value
        )
    }
}

struct Sub {
    let value: Int

    func dispatch(_ state: SuperState) -> SuperState {
        SuperState(
            state1: self.dispatch(state.state1),
            state2: self.dispatch(state.state2)
        )
    }

    func dispatch(_ subject: State) -> State {
        State(
            value: subject.value - value
        )
    }

    func dispatch(_ subject: OtherState) -> OtherState {
        OtherState(
            value: subject.value - value
        )
    }
}

extension SuperState {
    func dispatch(_ modifier: Add) -> SuperState {
        modifier.dispatch(self)
    }

    func dispatch(_ modifier: Sub) -> SuperState {
        modifier.dispatch(self)
    }
}

extension State {
    func dispatch(_ modifier: Add) -> State {
        modifier.dispatch(self)
    }

    func dispatch(_ modifier: Sub) -> State {
        modifier.dispatch(self)
    }
}

var state = SuperState(
    state1: State(value: 0),
    state2: OtherState(value: 0)
)

state = state.dispatch(Add(value: 1))
print(state)
state = state.dispatch(Sub(value: 1))
print(state)
state = state.dispatch(Add(value: 1))
print(state)

Here, there is no generic/protocol and it works fine, but it forces you to write a lot of boiler plate like the dispatch method on the state that is basically just

modifier.dispatch(self)

Where the modifier is a different type and thanks to method overloading in swift it's fairly easy to implement with some boiler plate code.

You'd be tempted to write protocol like this to save some typing. Given the constraint of generic, we can't use generic functions in the protocol because it would still fail when trying to return either a State or SuperState... because swift doesn't generate specialized functions in a way it's useful here.

For example you could have the following protocol:

protocol Dispatchable {
    func dispatch<T: Dispatch>(_ state: T) -> T
}

protocol Dispatch {
    func dispatch<T: Dispatchable>(_ modifier: T) -> Self
}

extension Dispatch { 
    func dispatch<T: Dispatchable>(_ modifier: T) -> Self {
        print("default dispatch called")
        return modifier.dispatch(self)
    }
}

extension Dispatchable {
    func dispatch<T: Dispatch>(_ state: T) -> T {
        print("default dispatchable called")
        return state
    }       
}

And an extension like this for the Sub modifier.

extension Sub: Dispatchable {
    func dispatch<T: Dispatch>(_ state: T) -> T {
        if let state = state as? SuperState {
            return self.dispatch(state) as! T
        } else if let state = state as? State {
            return self.dispatch(state) as! T
        } else if let state = state as? OtherState {
            return self.dispatch(state) as! T
        } else {
            return state
        }
    }

    func dispatch(_ state: SuperState) -> SuperState {
        SuperState(
            state1: state.state1.dispatch(self),
            state2: state.state2.dispatch(self)
        )
    }

    func dispatch(_ state: State) -> State {
        State(value: state.value + value)
    }

    func dispatch(_ state: OtherState) -> OtherState {
        OtherState(value: state.value + value)
    }
}

This works but there's no real way to get rid of dynamic dispatch with the generic method and that also mean you just can't add new types without overriding the generic dispatch method.

I could get this to work using only static dispatch but the protocol declaration look like this:

protocol Dispatchable {
}

protocol Dispatch {
}

extension Dispatchable {
    func dispatch(_ state: SuperState) -> SuperState {
        return state
    }

    func dispatch(_ state: State) -> State {
        return state
    }

    func dispatch(_ state: OtherState) -> OtherState {
        return state
    }
}

extension Dispatch {
    func dispatch(_ modifier: Add) -> SuperState {
        modifier.dispatch(self as! SuperState)
    }
    func dispatch(_ modifier: Sub) -> SuperState {
        modifier.dispatch(self as! SuperState)
    }
    func dispatch(_ modifier: Add) -> State {
        modifier.dispatch(self as! State)
    }
    func dispatch(_ modifier: Sub) -> State {
        modifier.dispatch(self as! State)
    }
    func dispatch(_ modifier: Add) -> OtherState {
        modifier.dispatch(self as! OtherState)
    }
    func dispatch(_ modifier: Sub) -> OtherState {
        modifier.dispatch(self as! OtherState)
    }
}

You can see how adding a new modifier or state type would force creating a lot more function on the protocol than needed.

I'm a bit lost here.

Static dispatch: SwiftFiddle - Swift Online Playground
Dynamic Dispatch: SwiftFiddle - Swift Online Playground

If I could get the generic thing to automatically call the specialized function it would be great.

A big problem here is that you aren't constraining your inputs properly. Looking at the code you originally posted, I think what you want is actually something more like:

Wrong implementation.
protocol Update {
    associatedtype State
    func apply(to state: State) -> State
}

struct Updator<State> {
    var state: State
    init(_ state: State) {
        self.state = state
    }
    func apply<T: Update>(update: T) -> T.State where T.State == State {
        update.apply(to: state)
    }
}

struct State {
    var value: Int
}

struct Add: Update {
    var value: Int
    func apply(to state: State) -> State {
        State(value: state.value + value)
    }
}

struct Sub: Update {
    var value: Int
    func apply(to state: State) ->  State {
        State(value: state.value - value)
    }
}

var state = State(value: 0)
print(state)
state = Updator(state).apply(update: Add(value: 1))
print(state)
state = Updator(state).apply(update: Sub(value: 1))
print(state)

Edit: Actually, I think you wanted this:

protocol Update<State> {
  associatedtype State
  func apply(to state: State) -> State
}

struct Updator<State> {
  var state: State
  var update: any Update<State>

  init(_ state: State, _ update: any Update<State>) {
    self.state = state
    self.update = update
  }
  func apply() -> State {
    update.apply(to: state)
  }
}

struct State {
  var value: Int
}

struct Add: Update {
  var value: Int
  func apply(to state: State) -> State {
    State(value: state.value + value)
  }
}

struct Sub: Update {
  var value: Int
  func apply(to state: State) -> State {
    State(value: state.value - value)
  }
}

var state = State(value: 0)
print(state)
state = Updator(state, Add(value: 1)).apply()
print(state)
state = Updator(state, Sub(value: 1)).apply()
print(state)

Is there a reason to use an existential for update rather than just make U: Update one of Updator's generic parameters?

Thank you but no, the issue with the protocol is that the associated type binds the Update to the State struct. So it's not possible to propagate the same action to leaves of nested structure.

Well, there really isn't a way to do this for arbitrary structures without dropping down to Mirror and dynamically iterating over your states at runtime. But you could do something like this:

protocol StateProtocol {
  associatedtype Value
  init(value: Value)
  var value: Value { get }
}

protocol Update<Value> {
  associatedtype Value
  func apply<State: StateProtocol>(to state: State) -> State where State.Value == Value
}

extension StateProtocol {
  func apply(update: any Update) -> Self {
    return if let update = update as? any Update<Value> {
      update.apply(to: self)
    } else {
      self
    }
  }
}

struct Aggregate<each State: StateProtocol> {
    var value: (repeat each State)
    init(_ state: repeat each State) {
      self.value = (repeat each state)
    }
    func apply(update: some Update) -> Self {
        return Self(repeat (each value).apply(update: update))
    }
}

extension Aggregate: CustomDebugStringConvertible {
  var debugDescription: String {
    String(reflecting: value)
  }
}

struct Add: Update {
  typealias Value = Int
  var value: Int
  func apply<State: StateProtocol>(to state: State) -> State where State.Value == Value {
    State(value: state.value + value)
  }
}
struct Sub: Update {
  typealias Value = Int
  var value: Int
  func apply<State: StateProtocol>(to state: State) -> State where State.Value == Value {
    State(value: state.value - value)
  }
}

struct State: StateProtocol {
  var value: Int
}

struct OtherState: StateProtocol {
  var value: Int
}

var superstate = Aggregate(
  State(value: 0),
  OtherState(value: 1)
)
print(superstate)
superstate = superstate.apply(update: Add(value: 1))
print(superstate)
superstate = superstate.apply(update: Sub(value: 1))
print(superstate)

Edit: I really wish that I could have done this instead of type erasure:

extension StateProtocol {
    func apply(update: some Update) -> Self {
        if let update = update as? some Update<Value> {
            return update.apply(to: self)
        } else {
            return self
        }
    }
}

This looks one of those cases when fighting boilerplate leads to ... a greater amount of boilerplate.

1 Like

How about some good ol' visitors?

protocol StateModifier {
    func dispatch(_ state: State) -> State
}

protocol OtherStateModifier {
    func dispatch(_ state: OtherState) -> OtherState
}

struct State {
    let value: Int
    
    func dispatch(_ modifier: some StateModifier) -> Self {
        modifier.dispatch(self)
    }
}

struct OtherState {
    let value: Int
    
    func dispatch(_ modifier: some OtherStateModifier) -> Self {
        modifier.dispatch(self)
    }
}

struct SuperState {
    let state1: State
    let state2: OtherState
    
    func dispatch(_ modifier: some StateModifier & OtherStateModifier) -> Self {
        Self(
            state1: state1.dispatch(modifier),
            state2: state2.dispatch(modifier)
        )
    }
}

struct Add: StateModifier, OtherStateModifier {
    let value: Int
    
    func dispatch(_ subject: State) -> State {
        State(value: subject.value + value)
    }

    func dispatch(_ subject: OtherState) -> OtherState {
        OtherState(value: subject.value + value)
    }
}

struct Sub: StateModifier, OtherStateModifier {
    let value: Int
    
    func dispatch(_ subject: State) -> State {
        State(value: subject.value - value)
    }

    func dispatch(_ subject: OtherState) -> OtherState {
        OtherState(value: subject.value - value)
    }
}

var state = SuperState(
    state1: State(value: 0),
    state2: OtherState(value: 0)
)

print(state)
state = state.dispatch(Add(value: 1))
print(state)
state = state.dispatch(Sub(value: 1))
print(state)
state = state.dispatch(Add(value: 1))
print(state)
1 Like