swift 意外的数字文本泛型类型推断行为

eiee3dmh  于 2023-04-04  发布在  Swift
关注(0)|答案(4)|浏览(112)

我试图在Swift 5.7中为任何数值(à la micrograd)创建一个通用 Package 器。到目前为止,我的实现是这样的:

struct Value<Scalar: SignedNumeric>: Equatable {
    
    var data: Scalar
    
    init(_ data: Scalar) {
        self.data = data
    }
}

// MARK: Value+SignedNumeric
extension Value: SignedNumeric {
    typealias IntegerLiteralType = Scalar.IntegerLiteralType
    typealias Magnitude = Scalar.Magnitude
    
    var magnitude: Magnitude {
        data.magnitude
    }
    
    init(integerLiteral value: IntegerLiteralType) {
        self.data = Scalar(integerLiteral: value)
    }
    
    init?<T>(exactly source: T) where T : BinaryInteger {
        if let numericData = Scalar(exactly: source) {
            self.data = numericData
        } else {
            return nil
        }
    }
    
    static func * (lhs: Value<Scalar>, rhs: Value<Scalar>) -> Value<Scalar> {
        return Value(lhs.data * rhs.data)
    }
    
    static func + (lhs: Value<Scalar>, rhs: Value<Scalar>) -> Value<Scalar> {
        return Value(lhs.data + rhs.data)
    }
    
    static func - (lhs: Value<Scalar>, rhs: Value<Scalar>) -> Value<Scalar> {
        return Value(lhs.data - rhs.data)
    }
    
    static func *= (lhs: inout Value<Scalar>, rhs: Value<Scalar>) {
        lhs.data = lhs.data * rhs.data
    }
}

我遇到的问题是,如果我只提供一个没有任何显式类型注解的整数字面量,下面的代码将无法编译:

func testScalarTypeInference() {
    // all of these compile fine
    Value(Double(1.0))
    Value<Double>(1)
    Value<Int>(1)
    Value(Int(1))
    Value(1 as Int)
    Value(1.0)

    // doesn't compile
    Value(1)  // Generic parameter 'Scalar' could not be inferred in cast to 'Value'
}

为什么编译器会推断出简单的Double文字(Value(1.0))的类型,而不是Int文字(Value(1))的类型?

编辑:最让人困惑的地方我忘了说了,这个问题只在Value尝试符合SignedNumeric时存在。下面的代码编译正常:

struct ValueWrapper<Scalar: SignedNumeric> {
    var data: Scalar
    
    init(_ data: Scalar) {
        self.data = data
    }
}

func testValueWrapperTypeInference() {
    type(of: ValueWrapper(1))    // Expression of type 'ValueWrapper<Int>.Type' is unused
    type(of: ValueWrapper(1.0))  // Expression of type 'ValueWrapper<Double>.Type' is unused
}

所以我想可能是其中一个init出了问题?

1cosmwyk

1cosmwyk1#

它实际上比SignedNumeric简单。对于ExpressibleByIntegerLiteral(SignedNumeric需要)也存在同样的问题:

struct Value<Scalar: ExpressibleByIntegerLiteral>: ExpressibleByIntegerLiteral {
    var data: Scalar

    init(integerLiteral value: Scalar.IntegerLiteralType) {
        self.data = Scalar(integerLiteral: value)
    }
}

Value(1) // Generic parameter 'Scalar' could not be inferred in cast to 'Value'

我不认为这是可能的建立你试图建立什么。(ExpressibleByIntegerLiteral上的init(_ value: Self)扩展可能存在歧义,这可能是它将其称为“强制转换”的原因。)如果您需要此功能,我建议打开bug report。我不相信可以将ExpressibleByIntegerLiteral创建为泛型 Package 器,因为您不能默认Scalar类型,这会妨碍你遵守SignedNumeric
SR-7476可能是相关的。

gojuced7

gojuced72#

为什么编译器会推断出简单的Double文字(Value(1.0))的类型,而不是Int文字(Value(1))的类型?
我想是因为SignedNumeric继承了ExpressibleByIntegerLiteral,但它没有继承ExpressibleByFloatLiteral
当编译器看到一个带有小数点的文字时,例如1.5,除非可以从上下文推断出浮点类型,否则它会假设Double,然后在这种情况下,它可以说Scalar必须是Double。这是一个特殊情况,因此人们不必明确let f = 1.5f的类型。
对于整数文字也有类似的规则,但不幸的是,它不适用,因为任何ExpressibleByIntegerLiteral都会覆盖该规则,即使没有,如果有两个Scalar类型,其ExpressibleByIntegerLiteral的实现使用int作为关联类型,则无法在它们之间进行选择。
至少我是这么想的。

kb5ga3dv

kb5ga3dv3#

编辑

实际上,这是令人困惑的,在阅读this answer之后,我尝试符合ExpressibleByFloatLiteral,这次ValueWrapper(1.0)触发了错误:

extension ValueWrapper: ExpressibleByFloatLiteral where Scalar: ExpressibleByFloatLiteral {
    init(floatLiteral value: Scalar.FloatLiteralType) {
        self.data = Scalar(floatLiteral: value)
    }
}

struct ValueWrapper<Scalar: SignedNumeric> {
    var data: Scalar
    
    init(_ data: Scalar) {
        self.data = data
    }
}
func testValueWrapperTypeInference() {
    type(of: ValueWrapper(1))    // Expression of type 'ValueWrapper<Int>.Type' is unused
    type(of: ValueWrapper(1.0))  // Generic parameter 'Scalar' could not be inferred in cast to 'ValueWrapper'
}

所以我认为这个问题来自于init(_ value: Self)* 的一些模糊性更合理。

原创理论

如果你看ExpressibleByFloatLiteral.FloatLiteralType

讨论

FloatLiteralType的有效类型为Float、Double和Float80(如果可用)。
对于ExpressibleByIntegerLiteral.IntegerLiteralType,您可以:

讨论

标准库整型和浮点类型都是IntegerLiteralType的有效类型。
例如,当你从一个字面量初始化一个浮点数时,它只能是一个浮点数,默认值是Double,但是当你使用一个整数字面量时,它可能是一个整数或浮点数,编译器会感到困惑。

wlsrxk51

wlsrxk514#

不知道为什么会失败(可能是与其他语言特性或其他构造函数冲突),但如果将调用从Value(1)更改为Value.init(1),它就开始工作了。另一种解决方法是在构造函数Value(data: 1)中使用命名参数。或者像这样键入:

let v: Value = .init(1)

相关问题