首页 > 解决方案 > 通过泛型约束泛型

问题描述

有人可以提供解决方案吗,正如我在底部所描述的那样,任何变体都无法通过(或不是???),但也许有人知道非常接近的解决方案?

PS 请参阅代码注释中的描述。

import Foundation

public struct HTTPRequest {
    // ...
}

public struct HTTPResponse {
    // ...
}

public class Router<SuccessResponse, FailureResponse: Swift.Error> {

    internal typealias Encoder = (Result<SuccessResponse, FailureResponse>) -> HTTPResponse
    internal typealias Responder = (HTTPRequest) -> HTTPResponse

    private let encoder: Encoder
    internal private(set) var responders: [String: Responder]

    internal init(encoder: @escaping Encoder) {
        self.encoder = encoder
        self.responders = [:]
    }

    // For me, is not correct! Description further...
    public func on(_ path: String, using closure: @escaping (HTTPRequest) -> Result<SuccessResponse, FailureResponse>) {
        responders[path] = { request in
            let result = closure(request)
            return self.encoder(result)
        }
    }

    // It's correct way, in usege, in this variant, you can't use different subtypes of SuccessResponse and FailureResponse, only one concrate type!
    // BUT I can't set constraint on SuccessResponse and FailureResponse as this should be a Protocol and in result we have error `... constrained to non-protocol, non-class type ...`
    public func on<S: SuccessResponse, F: FailureResponse>(_ path: String, using closure: @escaping (HTTPRequest) -> Result<S, F>) { // Type 'S, ''F' constrained to non-protocol, non-class type 'FailureResponse'
        responders[path] = { request in
            let result = closure(request)
            return self.encoder(result)
        }
    }

}

使用示例,您不能使用任何 ApiSuccess 或 ApiFailure,只能使用受其他泛型约束的具体类型:

protocol ApiSuccess {
    // ...
}
protocol ApiFailure {
    // ...
}

enum Endpoint1Success: ApiSuccess {
    case ok
    case empty
}
enum Endpoint1Failure: ApiFailure {
    case not
    case internalError
}


let router = Router<ApiSuccess, ApiFailure> { result -> HTTPResponse in
    switch result {
    case .success(let apiSuccess):
        // apiSuccess encoded to HTTPResponse
        return HTTPResponse()
    case .failure(let apiFailure):
        // apiFailure encoded to HTTPResponse
        return HTTPResponse()
    }
}

router.on("/ok") { request -> Result<Endpoint1Success, Endpoint1Failure> in
    return .success(.ok)
}

router.on("/not") { request -> Result<Endpoint1Success, Endpoint1Failure> in
    return .failure(.not)
}

或者也许不是正确的方法?

标签: swiftgenerics

解决方案


What you are trying to do is generic variance, which is only natively supported for arrays.

So here's a workaround for your usage example. But before that, your example needs to be fixed first, as it doesn't currently compile. ApiFailures should be a class conforming to Error and Endpoint1Failure should be a class as well. Its cases should be rewritten to static lets.

The workaround is to only use the non-generic on method, and write the caller like this:

router.on("/ok") { request in
    return Result<Endpoint1Success, Endpoint1Failure>.success(.ok)
        .map { $0 }.mapError { $0 }
}

Essentially, you add .map { $0 }.mapError { $0 } after every return statement. This is the way you cast from Result<S, F> to Result<ApiSuccess, ApiFailure>. If you don't want to write this all the time, you can extract this into an extension:

extension Result where Success : ApiSuccess, Failure : ApiFailure {
    func toGeneralApiResult() -> Result<ApiSuccess, ApiFailure> {
        map { $0 }.mapError { $0 }
    }
}

This cast is actually unavoidable, even if you could constrain your S and F parameters. The only difference is where you do this cast. If you could constrain S and F, you would cast result before passing it to encoder. In this workaround you're just doing it on the caller's side.


推荐阅读