为什么Swift可以在没有足够信息的情况下推断类型?

qc6wkl3g  于 2023-06-21  发布在  Swift
关注(0)|答案(1)|浏览(103)

我写了一个简单的fetch客户端来处理请求。代码如下:
申请

public protocol RequestParameter: Encodable {
  var queryItems: [URLQueryItem] { get }
}

public extension RequestParameter {
  var queryItems: [URLQueryItem] {
    var this = [URLQueryItem]()
    let mirror = Mirror(reflecting: self)
    for child in mirror.children {
      guard let label = child.label, let value = child.value as? CustomStringConvertible else {
        continue
      }
      this.append(.init(name: label, value: value.description))
    }
    return this
  }
}

extension Dictionary: RequestParameter where Key == String, Value: CustomStringConvertible & Encodable {
  public var queryItems: [URLQueryItem] {
    var this = [URLQueryItem]()
    for (key, value) in self {
      this.append(.init(name: key, value: value.description))
    }
    return this
  }
}

public protocol Request<Parameter, Response> {
  associatedtype Parameter: RequestParameter
  associatedtype Response: Decodable

  var method: HTTPMethod { get }
  var path: String { get }
  var parameter: Parameter? { get }
  var headers: HTTPHeaders? { get }
}

public extension Request {
  func build(_ urlString: String, encoder: JSONEncoder? = nil) -> URLRequest {
    var components = URLComponents(string: urlString)
    components?.path = path

    if method == .get {
      components?.queryItems = parameter?.queryItems
    }

    guard let url = components?.url else {
      fatalError("url can't be nil")
    }

    var urlRequest = URLRequest(url: url)
    urlRequest.httpMethod = method.rawValue

    if let headers {
      urlRequest.headers = headers
    }

    if method == .post, let parameter, let encoder {
      do {
        urlRequest.httpBody = try encoder.encode(parameter)
      } catch {
        fatalError("`parameter cannot be encoded`")
      }
    }

    return urlRequest
  }
}

客户端

public protocol Client {
  func send<RequestType: Request>(_ request: RequestType, encoder: JSONEncoder?, decoder: JSONDecoder) async throws -> RequestType.Response

  var urlString: String { get }

  static var shared: Self { get }
}

public extension Client {
  func send<RequestType: Request>(_ request: RequestType, encoder: JSONEncoder? = nil, decoder: JSONDecoder = .init()) async throws -> RequestType.Response {
    let (data, urlResponse) = try await URLSession.shared.data(for: request.build(urlString, encoder: encoder))
    guard let httpResponse = urlResponse as? HTTPURLResponse else {
      throw #InvalidResponse
    }
    guard 200 ... 299 ~= httpResponse.statusCode else {
      throw #NetworkFailure(httpResponse.statusCode)
    }
    return try decoder.decode(RequestType.Response.self, from: data)
  }
}

因此,一个简单而有效的网络请求代码看起来像这样:

struct FooRequest: Request {
  typealias Parameter = FooParameter
  typealias Response = Foo

  var method: HTTPMethod = .get
  var path: String = ""
  var parameter: FooParameter? = FooParameter()
  var headers: HTTPHeaders? = nil
}
struct FooParameter: RequestParameter {}
struct Foo: Decodable {}
struct FooClient: Client {
  var urlString: String = "fooURLString.com"

  static var shared: FooClient = Self()
}

let result = try await FooClient.shared.send(FooRequest())

但是有时候一个简单的GET请求可能不需要参数。为了避免重复代码var parameter: FooParameter? = nilvar parameter: BarParameter? = nil,我添加了一个NeverParameter类型:

public struct NeverParameter: RequestParameter {
  private init() {}
}

public extension Request where Parameter == NeverParameter {
  var parameter: Parameter? { nil }
}

这个时候,问题来了。我认为使用NeverParameter,请求应该是这样的:

struct FooRequest: Request {
  typealias Parameter = NeverParameter
  typealias Response = Foo

  var method: HTTPMethod = .get
  var path: String = ""
  var headers: HTTPHeaders? = nil
}

但实际上,注解掉typealias Parameter = NeverParameter仍然可以编译:

struct FooRequest: Request {
  // typealias Parameter = NeverParameter
  typealias Response = Foo

  var method: HTTPMethod = .get
  var path: String = ""
  var headers: HTTPHeaders? = nil
}

(lldb)po类型(的:参数)
Swift.Optional<Request.NeverParameter>
为什么编译器可以推断出参数的类型是NeverParameter?即使默认值nil只添加在NeverParameter的where子句下?再添加一个具有相同实现的NeverParameter1,Xcode最终抛出了一个错误。

public struct NeverParameter: RequestParameter {
  private init() {}
}

public extension Request where Parameter == NeverParameter {
  var parameter: Parameter? { nil }
}

public struct NeverParameter1: RequestParameter {
  private init() {}
}

public extension Request where Parameter == NeverParameter1 {
  var parameter: Parameter? { nil }
}

错误:
类型“FooRequest”不符合协议“Request”

dsekswqp

dsekswqp1#

关联类型推理在语言中已经存在很长时间了。琐碎的情况是:

protocol Foo {
    associatedtype T
    func methodThatReturnsT() -> T
}

class Bar: Foo {
    func methodThatReturnsT() -> Int { 1 }
}

这不一定是编译器的实现方式,但你可以想象编译器会想:
Bar没有T类型。methodThatReturnsT应该返回TBar.methodThatReturnsT返回一个Int,所以T必须是Int
你在这里的情况基本上是一样的,只是有点不那么直观,因为它涉及到一个条件扩展。这种行为is found to be rather "magical" by some people,甚至reported as a bug。这是*************************。
这不一定是编译器的实现方式,但你可以想象编译器会想:
FooRequest没有parameter的声明。但是它是通过扩展名extension Request where Parameter == NeverParameter实现的。让我们使用这里声明的parameter
FooRequest也没有Parameter类型,但是parameter应该是Parameter类型。FooRequest.parameter(来自扩展名)的类型是NeverParameter,所以T必须是NeverParameter
这种关联的类型推断使得实现一些标准库协议比其他方式容易得多。例如,考虑OptionSet的典型实现。

struct Options: OptionSet {
    let rawValue: Int
    
    static let option1 = Options(rawValue: 1)
    static let option2 = Options(rawValue: 2)
    static let option3 = Options(rawValue: 4)
}

这里涉及哪些关联类型?OptionSet继承自RawRepresentableSetAlgebra,而SetAlgebra又继承自ExpressibleByArrayLiteral,因此有:

  • RawValue
  • Element
  • ArrayLiteralElement

RawValue是根据声明的rawValue属性推断出来的,这很直观。OptionSetSelf声明为Element的默认值,所以这没有问题。ArrayLiteralElement是如何被推断出来的?没有声明init(arrayLiteral:)初始化器,因此不能从中推断。
这与FooRequest的情况相同-SetAlgebra的 * 条件扩展 * 帮助推断ArrayLiteralElementElement相同,并且该扩展为init(arrayLiteral:)提供了默认实现。

extension SetAlgebra where Element == ArrayLiteralElement {
    public init(arrayLiteral: Element...) { ... }
}

请参阅Swift源代码中的扩展声明,或参阅此处的默认实现文档。

相关问题