ExpressibleByIntegerLiteral 结构体的初始值是否可以限制?

huangapple go评论65阅读模式
英文:

Is it possible to limit init values for ExpressibleByIntegerLiteral struct?

问题

我想实现一个Digit结构体,它应该可以通过整数字面量进行初始化。

像这样:

let digit: Digit = 5

但是Swift不应该允许这样做(或者作为选项,应该引发异常):

let digit: Digit = 15

我写了这段代码,但它看起来不太好:

struct Digit: ExpressibleByIntegerLiteral, Equatable, CustomStringConvertible {
    
    typealias IntegerLiteralType = Int
    
    var description: String {
        String(value)
    }
    
    var value: IntegerLiteralType {
        // Unfortunately, set throws can't be implemented, only get throws.
        willSet(newValue) {
            if 0...9 ~= value {
                self.value = newValue
            }
        }
    }

    init(integerLiteral: IntegerLiteralType) {
        if 0...9 ~= integerLiteral {
            value = integerLiteral
        } else {
            // This is definitely not good and may cause side effects.
            // But init for ExpressibleByIntegerLiteral can't be failable.
            value = 0
        }
    }
    
    init?(from char: Character?) {
        guard let char,
              let newValue = IntegerLiteralType(String(char)),
              0...9 ~= newValue
        else {
            return nil
        }
        value = newValue
    }
}

在Swift 5.8中,我该如何解决这个问题?我猜在下一个版本中可以使用宏来实现这一点,但不幸的是,我的Swift版本仍然没有宏。

谢谢。

英文:

I want to implement Digit struct which should be initialized by integer literal.

Like:

let digit: Digit = 5

But swift shouldn't allow doing this (or, as an option, an exception should be raised):

let digit: Digit = 15

I wrote this code but it's not looking good:

   struct Digit: ExpressibleByIntegerLiteral, Equatable, CustomStringConvertible {
    
    typealias IntegerLiteralType = Int
    
    var description: String {
        String(value)
    }
    
    var value: IntegerLiteralType {
        // Unfortunately, set throws can't be implemented, only get throws.
        willSet(newValue) {
            if 0...9 ~= value {
                self.value = newValue
            }
        }
    }


    init(integerLiteral: IntegerLiteralType) {
        if 0...9 ~= integerLiteral {
            value = integerLiteral
        } else {
            // This is definitely not good and may cause side effects.
            // But init for ExpressibleByIntegerLiteral can't be failable.
            value = 0
        }
    }
    
    init?(from char: Character?) {
        guard let char,
              let newValue = IntegerLiteralType(String(char)),
              0...9 ~= newValue
        else {
            return nil
        }
        value = newValue
    }
}

How may I overcome this in Swift 5.8? I guess in next version Macros may be used for this but unfortunately I my Swift version still doesn't have them..

Thank you.

答案1

得分: 1

无法为除0-9之外的所有数字生成编译器错误,您最好能够最大限度地扩展会导致编译器错误的数字范围,方法是使用最小的 IntegerLiteralType,并对其他数字执行 fatalError

struct Digit: ExpressibleByIntegerLiteral {
    let value: UInt8
    
    init(integerLiteral value: UInt8) {
        guard (0...9).contains(value) else {
            fatalError("Digit must be between 0 and 9!")
        }
        self.value = value
    }
}

由于我使用了 UInt8 作为 IntegerLiteralType,因此像这样的情况将无法编译通过:

let x: Digit = 1000

而像这样的情况会触发 fatalError

let x: Digit = 10

由于只有10个有效值,您可以考虑的另一种方法是使用枚举。枚举情况不能以数字开头,所以您需要在前面加上一些内容:

enum Digit: UInt8 {
    case _0
    case _1
    case _2
    case _3
    case _4
    case _5
    case _6
    case _7
    case _8
    case _9
}
英文:

Producing an compiler error for all numbers except 0-9 is not possible, the best you can do is to maximise the range of numbers that would produce a compiler error, by using the smallest IntegerLiteralType, and do fatalError for other numbers.

struct Digit: ExpressibleByIntegerLiteral {
    let value: UInt8
    
    init(integerLiteral value: UInt8) {
        guard (0...9).contains(value) else {
            fatalError("Digit must be between 0 and 9!")
        }
        self.value = value
    }
}

Since I used UInt8 as the IntegerLiteralType, things like this would not compile:

let x: Digit = 1000

And things like this would fatalError:

let x: Digit = 10

<hr>

Since there are only 10 valid values for this, an alternative you could consider is using an enum. Enum cases can't start with digits, so you'd need something in front:

enum Digit: UInt8 {
    case _0
    case _1
    case _2
    case _3
    case _4
    case _5
    case _6
    case _7
    case _8
    case _9
}

答案2

得分: 0

由于 Sweeper 的帮助,我使用前置条件编写了以下代码,以便即使在生产环境中也会在不良情况下崩溃:

fileprivate let wrongDigitError = "Fatal error: digit should be between 0 and 9!"

struct Digit: ExpressibleByIntegerLiteral, Equatable, CustomStringConvertible {

    typealias IntegerLiteralType = UInt8

    var description: String {
        String(value)
    }

    var value: IntegerLiteralType {
        willSet(newValue) {
            precondition(0...9 ~= value, wrongDigitError)
            self.value = newValue
        }
    }

    init(integerLiteral: IntegerLiteralType) {
        precondition(0...9 ~= integerLiteral, wrongDigitError)
        self.value = integerLiteral
    }

    init?(from char: Character?) {
        guard let char,
              let newValue = IntegerLiteralType(String(char)),
              0...9 ~= newValue
        else {
            return nil
        }
        value = newValue
    }
}

请注意,我没有翻译代码,只返回了代码的原文。

英文:

Thanks to Sweeper, I wrote the code this way using precondition so it will crash in bad cases even in production:

fileprivate let wrongDigitError = &quot;Fatal error: digit should be between 0 and 9!&quot;


struct Digit: ExpressibleByIntegerLiteral, Equatable, CustomStringConvertible {
    
    typealias IntegerLiteralType = UInt8
    
    var description: String {
        String(value)
    }
    
    var value: IntegerLiteralType {
        willSet(newValue) {
            precondition(0...9 ~= value, wrongDigitError)
            self.value = newValue
        }
    }

    init(integerLiteral: IntegerLiteralType) {
        precondition(0...9 ~= integerLiteral, wrongDigitError)
        self.value = integerLiteral
    }
    
    init?(from char: Character?) {
        guard let char,
              let newValue = IntegerLiteralType(String(char)),
              0...9 ~= newValue
        else {
            return nil
        }
        value = newValue
    }

huangapple
  • 本文由 发表于 2023年7月20日 16:03:47
  • 转载请务必保留本文链接:https://go.coder-hub.com/76727822.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定