使用nil-coalescing运算符和两个选项时,类型推断失败 [英] Type inference fails when using nil-coalescing operator with two optionals

查看:109
本文介绍了使用nil-coalescing运算符和两个选项时,类型推断失败的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我们正试图确定这是Swift中的一个bug还是我们滥用泛型,选项,类型推断和/或nil合并运算符。

我们的框架包含一些代码将字典解析为模型,并且我们遇到了可选属性和默认值的问题。



$ p $ mapped< T>(...) - > $>在协议扩展中定义的两个通用函数: T'
映射< T:SomeProtocol>(...) - > T'

我们的结构和类遵循这个协议,然后在协议要求的初始化函数内解析它们的属性。

init(...)函数中,我们尝试设置属性 someNumber 像这样:

  someNumber = self.mapped(dictionary,key:someNumber )?? someNumber 

字典当然包含键 someNumber 。但是,这总是会失败,并且实际值永远不会从 mapped()函数返回。



注释掉第二个泛型函数或强制在作业的rhs上下注值将解决此问题,但我们认为这应该按照它当前写入的方式运行。






下面是一个完整的代码片段,演示了这个问题,还有两个选项(暂时)修复了标记为选项1 OPTION 2 代码:

  import Foundation 

//一些协议

协议SomeProtocol {
init(字典:NSDictionary?)
}

扩展SomeProtocol {
func mapped< ; T>(字典:NSDictionary ?,键:字符串) - > T' {
guard let dictionary = dictionary else {
return nil
}

let source = dictionary [key]
switch source {

的情况是T:
的返回源为? T

默认值:
中断
}

返回零
}

// --- ---
//选项1:注释掉它使得它工作
// ---

func mapped< T其中T:SomeProtocol>(字典:NSDictionary ?, key:String) - > T' {
return nil
}
}

//某结构

结构SomeStruct {
var someNumber:Double? = 0.0
}

扩展SomeStruct:SomeProtocol {
init(dictionary:NSDictionary?){
someNumber = self.mapped(dictionary,key:someNumber) ?? someNumber

//选项2:编写它使得它工作
// someNumber = self.mapped(dictionary,key:someNumber)?? someNumber!


$ b $ //测试代码

let test = SomeStruct(字典:NSDictionary(object:1234.4567,forKey:someNumber))
if test.someNumber == 1234.4567 {
print(success \(test.someNumber!))
} else {
print(failure \(test。 someNumber))
}

请注意,这是一个错过了实际映射函数的实现,但结果是相同的,为了这个问题,代码应该是足够的。



< hr>

编辑:我之前报告过这个问题,现在它被标记为固定的,所以希望这不应该发生在Swift 3中。

https://bugs.swift.org/browse/SR-574

解决方案

你给编译器提供了太多的选择,并且选错了一个(至少不是那个您通缉)。问题是每一个 T 可以平凡地提升到 T?,包括 T?(提升至 T ?? )。

  someNumber = self.mapped(dictionary,key:someNumber)?? someNumber 

哇。这种类型。所以可选。 :D



那么Swift如何开始把这件事情弄清楚。那么, someNumber Double?,所以它会尝试将它变成:

  Double? =双? ??双? 

这是否有效?让我们寻找一个通用的映射的,从最具体的开始。

  func mapped< T where T:SomeProtocol>(dictionary:NSDictionary?,key:String) - > T' {

要做到这一点, T Double?。是 Double?:SomeProtocol ?不。

  func mapped< T>(dictionary:NSDictionary?,key:String) - > T' {

这是否正常工作?当然! T 可以是 Double?我们返回 Double ?? 和一切都解决了。



那么为什么这个工作呢?

  someNumber = self.mapped(dictionary,key:someNumber)?? someNumber! 

这解决了:

 双? =可选(Double??? Double)

然后事情按您认为应该的方式工作到。



请注意这么多选项。 someNumber 真的必须是可选的吗?这些东西中的任何一个 throw ? (我并不建议> throw 是可选问题的一般解决方法,但至少这个问题让你有时间考虑这是否真的是错误条件。)

通过映射的方式,在Swift中专门针对返回值类型参数化几乎总是一个坏主意。这在Swift中(或任何具有大量类型推断的泛型语言)往往是一团糟,但在涉及Optionals的情况下,它在Swift中确实爆发了。类型参数通常应该出现在参数中。如果您尝试如下所示,则会看到问题:

  let x = test.mapped(...)

无法推断 x 。这不是一种反模式,有时候这种麻烦是值得的(公平地说,你正在解决的问题可能就是其中一种情况),但如果可以的话,就避免它。



但是它是可选择的东西正在消灭你。




编辑:Dominik问一个很好的问题关于为什么当删除映射的受限版本时,它的行为有所不同。我不知道。很显然,类型匹配引擎会根据 mapped 通用的方式,以稍微不同的顺序检查有效类型。您可以通过将 print(T.self)添加到映射的< T> 中来查看。这可能被认为是编译器中的一个错误。


We are trying to figure whether this is a bug in Swift or us misusing generics, optionals, type inference and/or nil coalescing operator.

Our framework contains some code for parsing dictionaries into models and we've hit a problem with optional properties with default values.

We have a protocol SomeProtocol and two generic functions defined in a protocol extension:

mapped<T>(...) -> T?
mapped<T : SomeProtocol>(...) -> T?

Our structs and classes adhere to this protocol and then parse their properties inside an init function required by the protocol.

Inside the init(...) function we try to set a value of the property someNumber like this:

someNumber = self.mapped(dictionary, key: "someNumber") ?? someNumber

The dictionary of course contains the actual value for key someNumber. However, this will always fail and the actual value will never get returned from the mapped() function.

Either commenting out the second generic function or force downcasting the value on the rhs of the assignment will fix this issue, but we think this should work the way it currently is written.


Below is a complete code snippet demonstrating the issue, along with two options that (temporarily) fix the issue labeled OPTION 1 and OPTION 2 in the code:

import Foundation

// Some protocol

protocol SomeProtocol {
    init(dictionary: NSDictionary?)
}

extension SomeProtocol {
    func mapped<T>(dictionary: NSDictionary?, key: String) -> T? {
        guard let dictionary = dictionary else {
            return nil
        }

        let source = dictionary[key]
        switch source {

        case is T:
            return source as? T

        default:
            break
        }

        return nil
    }

    // ---
    // OPTION 1: Commenting out this makes it work
    // ---

    func mapped<T where T:SomeProtocol>(dictionary: NSDictionary?, key: String) -> T? {
        return nil
    }
}

// Some struct

struct SomeStruct {
    var someNumber: Double? = 0.0
}

extension SomeStruct: SomeProtocol {
    init(dictionary: NSDictionary?) {
        someNumber = self.mapped(dictionary, key: "someNumber") ?? someNumber

        // OPTION 2: Writing this makes it work
        // someNumber = self.mapped(dictionary, key: "someNumber") ?? someNumber!
    }
}

// Test code

let test = SomeStruct(dictionary: NSDictionary(object: 1234.4567, forKey: "someNumber"))
if test.someNumber == 1234.4567 {
    print("success \(test.someNumber!)")
} else {
    print("failure \(test.someNumber)")
}

Please note, that this is an example which misses the actual implementations of the mapped functions, but the outcome is identical and for the sake of this question the code should be sufficient.


EDIT: I had reported this issue a while back and now it was marked as fixed, so hopefully this shouldn't happen anymore in Swift 3.
https://bugs.swift.org/browse/SR-574

解决方案

You've given the compiler too many options, and it's picking the wrong one (at least not the one you wanted). The problem is that every T can be trivially elevated to T?, including T? (elevated to T??).

someNumber = self.mapped(dictionary, key: "someNumber") ?? someNumber

Wow. Such types. So Optional. :D

So how does Swift begin to figure this thing out. Well, someNumber is Double?, so it tries to turn this into:

Double? = Double?? ?? Double?

Does that work? Let's look for a generic mapped, starting at the most specific.

func mapped<T where T:SomeProtocol>(dictionary: NSDictionary?, key: String) -> T? {

To make this work, T has to be Double?. Is Double?:SomeProtocol? Nope. Moving on.

func mapped<T>(dictionary: NSDictionary?, key: String) -> T? {

Does this work? Sure! T can be Double? We return Double?? and everything resolves.

So why does this one work?

someNumber = self.mapped(dictionary, key: "someNumber") ?? someNumber!

This resolves to:

Double? = Optional(Double? ?? Double)

And then things work the way you think they're supposed to.

Be careful with so many Optionals. Does someNumber really have to be Optional? Should any of these things throw? (I'm not suggesting throw is a general work-around for Optional problems, but at least this problem gives you a moment to consider if this is really an error condition.)

It is almost always a bad idea to type-parameterize exclusively on the return value in Swift the way mapped does. This tends to be a real mess in Swift (or any generic language that has lots of type inference, but it really blows up in Swift when there are Optionals involved). Type parameters should generally appear in the arguments. You'll see the problem if you try something like:

let x = test.mapped(...)

It won't be able to infer the type of x. This isn't an anti-pattern, and sometimes the hassle is worth it (and in fairness, the problem you're solving may be one of those cases), but avoid it if you can.

But it's the Optionals that are killing you.


EDIT: Dominik asks a very good question about why this behaves differently when the constrained version of mapped is removed. I don't know. Obviously the type matching engine checks for valid types in a little different order depending on how many ways mapped is generic. You can see this by adding print(T.self) to mapped<T>. That might be considered a bug in the compiler.

这篇关于使用nil-coalescing运算符和两个选项时,类型推断失败的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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