基于消息的体系结构中的通用消息

zou*_*oul 7 generics elm swift

我正在Swift中尝试基于消息的架构.我正在尝试做类似于Elm Architecture的事情.这是我的代码的样子:

enum SideEffect<Message> {

    case sendRequest((String) -> Message)
}

protocol Component {

    associatedtype Message

    mutating func send(msg: Message) -> [SideEffect<Message>]
}

struct State: Component {

    var something: String?

    enum Message {

        case downloadSomething
        case receiveResponse(String)
    }

    mutating func send(msg: Message) -> [SideEffect<Message>] {
        switch msg {
            case .downloadSomething:
                return [.sendRequest(Message.receiveResponse)]
            case .receiveResponse(let response):
                something = response
                return []
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

所以状态是建模的State,你可以通过发送Messages 来改变它.如果有任何副作用要计算,它们将作为SideEffect消息返回,并由其他人处理.每条SideEffect消息都带有一个"回调"参数,a Message在副作用结束时发送.这非常有效.

现在,如果我想要一个通用的副作用消息怎么办?我想要这样的东西:

struct Request<ReturnType> { … }
Run Code Online (Sandbox Code Playgroud)

并且有相关的副作用来加载请求并返回类型的值ReturnType:

enum SideEffect<Message> {
    case sendRequest(Request<T>, (T) -> Message)
}
Run Code Online (Sandbox Code Playgroud)

但是(显然)这不会编译,因为它case必须是通用的T.我不能完全SideEffect通用T,因为还有其他副作用与之无关T.

我能以某种方式创建SideEffect一个消息Request<T>以后将派遣MessageT?(我想我想要在swift-evolution上讨论这个功能.)

Ham*_*ish 1

您需要输入擦除T\xe2\x80\x93,通常这可以通过闭包来完成,因为它们可以从创建它们的站点引用上下文,而不将该上下文暴露给外界。

\n\n

例如,使用模拟Request<T>(假设它是异步操作):

\n\n
struct Request<T> {\n\n    var mock: T\n\n    func doRequest(_ completion: @escaping (T) -> Void) {\n        // ...\n        completion(mock)\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

我们可以构建一个RequestSideEffect<Message>包含接受给定(Message) -> Void回调的闭包,然后对捕获的实例执行请求Request<T>,通过 a 转发结果(T) -> Message,然后可以将结果传递回回调(从而保持类型变量T\'包含在闭包中):

\n\n
struct RequestSideEffect<Message> {\n\n    private let _doRequest: (@escaping (Message) -> Void) -> Void\n\n    init<T>(request: Request<T>, nextMessage: @escaping (T) -> Message) {\n        self._doRequest = { callback in\n            request.doRequest {\n                callback(nextMessage($0))\n            }\n        }\n    }\n\n    func doRequest(_ completion: @escaping (Message) -> Void) {\n        _doRequest(completion)\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

现在你SideEffect<Message>可以看起来像这样:

\n\n
enum SideEffect<Message> {\n    case sendRequest(RequestSideEffect<Message>)\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

你可以State这样实现:

\n\n
protocol Component {\n    associatedtype Message\n    mutating func send(msg: Message) -> [SideEffect<Message>]\n}\n\nstruct State: Component {\n\n    var something: String\n\n    enum Message {\n        case downloadSomething\n        case receiveResponse(String)\n    }\n\n    mutating func send(msg: Message) -> [SideEffect<Message>] {\n        switch msg {\n        case .downloadSomething:\n            let sideEffect = RequestSideEffect(\n                request: Request(mock: "foo"), nextMessage: Message.receiveResponse\n            )\n            return [.sendRequest(sideEffect)]\n        case .receiveResponse(let response):\n            something = response\n            return []\n        }\n    }\n}\n\nvar s = State(something: "hello")\nlet sideEffects = s.send(msg: .downloadSomething)\n\nfor case .sendRequest(let sideEffect) in sideEffects {\n    sideEffect.doRequest {\n        _ = s.send(msg: $0) // no side effects expected\n        print(s) // State(something: "foo")\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n