Swift为什么不使用更具体的类型调用我的重载方法? [英] Why doesn’t Swift call my overloaded method with a more specific type?

查看:93
本文介绍了Swift为什么不使用更具体的类型调用我的重载方法?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我使用可解码从JSON解码一个简单的结构.这符合Decodable协议的要求:

I use Decodable to decode a simple struct from JSON. This works by conforming to a Decodable protocol:

extension BackendServerID: Decodable {

    static func decode(_ json: Any) throws -> BackendServerID {
        return try BackendServerID(
            id: json => "id",
            name: json => "name"
        )
    }
}

不过,我希望能够用String呼叫decode,因此我添加了一个分机号:

I’d like to be able to call decode with a String, though, so I have added an extension:

extension Decodable {

    static func decode(_ string: String) throws -> Self {
        let jsonData = string.data(using: .utf8)!
        let jsonObject = try JSONSerialization.jsonObject(with: jsonData, options: [])
        return try decode(jsonObject)
    }
}

然后我要对这样的对象进行解码:

Then I would like to decode the objects like this:

XCTAssertNoThrow(try BackendServerID.decode("{\"id\": \"foo\", \"name\": \"bar\"}"))

这不能按预期方式工作,因为以某种方式调用了decode(Any)方法而不是decode(String).我究竟做错了什么? (当我通过将自定义方法重命名为decodeString来澄清调用时,它可以正常工作.)

This doesn’t work as expected, though, since somehow the decode(Any) method gets called instead of decode(String). What am I doing wrong? (When I clarify the call by renaming my custom method to decodeString, it works correctly.)

推荐答案

我同意这种行为令人惊讶,并且您很可能希望

I would agree that this behaviour is surprising, and you may well want to file a bug over it.

快速浏览 CSRanking.cpp ,它是类型检查器实现的一部分,用于处理涉及重载解析的不同声明的等级" –我们可以在以下实现中看到这一点:

From quickly looking through the source of CSRanking.cpp, which is the part of type checker implementation that deals with the "rankings" for different declarations when it comes to overload resolution – we can see that in the implementation of:

/// \brief Determine whether the first declaration is as "specialized" as
/// the second declaration.
///
/// "Specialized" is essentially a form of subtyping, defined below.
static bool isDeclAsSpecializedAs(TypeChecker &tc, DeclContext *dc,
                                  ValueDecl *decl1, ValueDecl *decl2) {

类型检查器认为,具体类型的重载比协议扩展的重载更专门"(

The type checker considers an overload in a concrete type to be more "specialised" than an overload in a protocol extension (source):

  // Members of protocol extensions have special overloading rules.
  ProtocolDecl *inProtocolExtension1 = outerDC1
                                         ->getAsProtocolExtensionContext();
  ProtocolDecl *inProtocolExtension2 = outerDC2
                                         ->getAsProtocolExtensionContext();
  if (inProtocolExtension1 && inProtocolExtension2) {
    // Both members are in protocol extensions.
    // Determine whether the 'Self' type from the first protocol extension
    // satisfies all of the requirements of the second protocol extension.
    bool better1 = isProtocolExtensionAsSpecializedAs(tc, outerDC1, outerDC2);
    bool better2 = isProtocolExtensionAsSpecializedAs(tc, outerDC2, outerDC1);
    if (better1 != better2) {
      return better1;
    }
  } else if (inProtocolExtension1 || inProtocolExtension2) {
    // One member is in a protocol extension, the other is in a concrete type.
    // Prefer the member in the concrete type.
    return inProtocolExtension2;
  }

并且在执行重载解析时,类型检查器将跟踪每个潜在重载的分数",并选择最高的那个.当一个给定的过载比另一个过载更专业"时,其得分将增加,因此,它会受到青睐.还有其他因素可能会影响超载的得分,但是isDeclAsSpecializedAs似乎是这种情况下的决定性因素.

And when performing overload resolution, the type checker will keep track of a "score" for each potential overload, picking the one with the highest. When a given overload is considered more "specialised" than another, its score will be incremented, therefore meaning that it will be favoured. There are other factors that can affect an overload's score, but isDeclAsSpecializedAs appears to be the deciding factor in this particular case.

因此,如果我们考虑一个最小的示例,则类似于 @Sulthan给出的示例:

So, if we consider a minimal example, similar to the one @Sulthan gives:

protocol Decodable {
    static func decode(_ json: Any) throws -> Self
}

struct BackendServerID {}

extension Decodable {
    static func decode(_ string: String) throws -> Self {
        return try decode(string as Any)
    }
}

extension BackendServerID : Decodable {
    static func decode(_ json: Any) throws -> BackendServerID {
        return BackendServerID()
    }
}

let str = try BackendServerID.decode("foo")

调用BackendServerID.decode("foo")时,BackendServerID具体类型的重载优先于协议扩展中的重载(BackendServerID重载在混凝土扩展中的事实类型在这里没有区别).在这种情况下,这与功能签名本身是否专业化无关. 位置更为重要.

When calling BackendServerID.decode("foo"), the overload in the concrete type BackendServerID is preferred to the overload in the protocol extension (the fact that the BackendServerID overload is in an extension of the concrete type doesn't make a difference here). In this case, this is regardless of whether one is more specialised when it comes to the function signature itself. The location matters more.

(尽管如果涉及泛型,函数签名起作用–请参见下面的切线)

(Although the function signature does matter if generics are involved – see tangent below)

值得注意的是,在这种情况下,我们可以通过在调用时强制转换方法来强制Swift使用我们想要的重载:

It's worth noting that in this case we can force Swift to use the overload we want by casting the method at the call:

let str = try (BackendServerID.decode as (String) throws -> BackendServerID)("foo")

这现在将在协议扩展中称为重载.

This will now call the overload in the protocol extension.

如果重载都在BackendServerID中定义:

extension BackendServerID : Decodable {
    static func decode(_ json: Any) throws -> BackendServerID {
        return BackendServerID()
    }

    static func decode(_ string: String) throws -> BackendServerID {
        return try decode(string as Any)
    }
}

let str = try BackendServerID.decode("foo")

类型检查器实现中的上述条件不会触发,协议扩展中也不会触发–因此,在涉及重载解析时,更多的特殊"重载将完全基于签名.因此,将为String参数调用String重载.

The the above condition in the type checker implementation won't be triggered, as neither are in a protocol extension – therefore when it comes to overload resolution, the more "specialised" overload will be solely based on signatures. Therefore the String overload will be called for a String argument.

(关于泛型重载的正切线...)

值得注意的是,类型检查器中还有(很多)其他规则,用于确定一个重载是否比另一个重载更专业化".其中之一是优先选择非泛型重载,而不是泛型重载():

It's worth noting that there are (lots of) other rules in the type checker for whether one overload is considered more "specialised" than another. One of these is preferring non-generic overloads to generic overloads (source):

  // A non-generic declaration is more specialized than a generic declaration.
  if (auto func1 = dyn_cast<AbstractFunctionDecl>(decl1)) {
    auto func2 = cast<AbstractFunctionDecl>(decl2);
    if (func1->isGeneric() != func2->isGeneric())
      return func2->isGeneric();
  }

此条件的实现比协议扩展条件要高 –因此,如果您要更改协议中的decode(_:)要求,以使其使用通用占位符:

This condition is implemented higher up than the protocol extension condition – therefore if you were to change the decode(_:) requirement in the protocol such that it used a generic placeholder:

protocol Decodable {
    static func decode<T>(_ json: T) throws -> Self
}

struct BackendServerID {}

extension Decodable {
    static func decode(_ string: String) throws -> Self {
        return try decode(string as Any)
    }
}

extension BackendServerID : Decodable {
    static func decode<T>(_ json: T) throws -> BackendServerID {
        return BackendServerID()
    }
}

let str = try BackendServerID.decode("foo")

尽管已在协议扩展中,但现在将调用String重载而不是一般的重载.

The String overload will now be called instead of the generic one, despite being in a protocol extension.

实际上,正如您所看到的,有许多复杂因素决定了要调用哪个重载.正如其他人已经说过的,在这种情况下,最好的解决方案是通过给String重载一个参数标签来明确消除重载的歧义:

So really, as you can see, there are lots of complicated factors that determine which overload to call. Really the best solution in this case, as others have already said, is to explicitly disambiguate the overloads by giving your String overload an argument label:

extension Decodable {
    static func decode(jsonString: String) throws -> Self {
        // ...
    }
}

// ...

let str = try BackendServerID.decode(jsonString: "{\"id\": \"foo\", \"name\": \"bar\"}")

这不仅可以清除重载分辨率,还可以使API更加清晰.仅使用decode("someString"),尚不清楚确切的字符串格式(XML?CSV?).现在,很明显它需要一个JSON字符串.

Not only does this clear up the overload resolution, it also makes the API clearer. With just decode("someString"), it wasn't clear exactly what format the string should be in (XML? CSV?). Now it's perfectly clear that it expects a JSON string.

这篇关于Swift为什么不使用更具体的类型调用我的重载方法?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆