【问题标题】:Generic protocol for basic types基本类型的通用协议
【发布时间】:2014-06-13 17:21:01
【问题描述】:

我一直在思考 swift 字符串格式化的可能实现,其中大部分归结为“使用 NSString(format:...)” 这一切都很好,但我想要一个简洁易读的格式,所以我决定实现类似 python 的 % 格式化操作符:

@infix func % (value:Double, format:String) -> String {
    return NSString(format:format, value)
}

这对我可以使用的 Double 非常有用:

println("PI = " + M_PI % "%.3f")

导致:

PI = 3.142

虽然我可以轻松创建其中的 5 个,但我想把它变成一个通用函数:

@infix func %<T> (value:T, format:String) -> String {
    return NSString(format:format, value)
}

但这会导致消息:

Could not find an overload for 'init' that accepts the supplied arguments

足够合理,我可以传入一个元组,或者同样非客观的 C 语言。 (请注意,要真正做到这种 Python 风格,我传入一个元组,但这是另一回事,超出了这个问题的范围)

我尝试声明自己的空协议并在 Double 上实现它,但它根本没有帮助。

protocol NSStringFormattable {}
extension Double : NSStringFormattable {}

@infix func % <T:NSStringFormattable> (value:T, format:String) -> String {
    return NSString(format:format, value)
}

我显然可以做一些事情,比如为每个类添加一个格式函数,然后根据格式函数定义运算符,但在许多方面,这并不比只定义 5 个不同的运算符重载更好。

protocol NSStringFormattable {
    func format(format:String) -> String
}

extension Double : NSStringFormattable {
    func format(format:String) -> String {
        return NSString(format:format, self)
    }
}

@infix func % <T:NSStringFormattable> (value:T, format:String) -> String {
    return value.format(format)
}

如何将 T 限制为只能传递给 NSString(format:...) 的那些类型?

【问题讨论】:

    标签: generics swift


    【解决方案1】:

    您非常接近,但您不需要固定长度的元组。这就是导致你头痛的原因。只需使用数组即可。

    @infix func % (values:CVarArg[], format:String) -> String {
      return NSString(format:format, arguments:getVaList(values))
    }
    
    [M_PI, 6] % "%.3f->%d"
    ==> "3.142->6"
    
    [M_PI, M_PI_2] % "%.3f %.3f"
    ==> "3.142 1.571"
    

    当然,这是高度类型不安全的,因为正如您所说,它是未经检查的 printf。

    顺便说一句,这甚至适用于混合类型的东西,以及非文字:

    let x = 1
    let y = 1.5
    let z = "yes"
    
    [x, y, z] % "%d, %.2f, %@"
    ==> "1, 1.50, yes"
    

    但是,我不知道那部分是否会变得脆弱。混合类型的文字被提升为NSArray,这似乎是自动做的一件危险的事情,所以他们可能会改变它。但是NSArray 可以作为CVarArg[] 接受。

    请注意,并非所有类型都可以通过这种方式进行转换。例如,角色目前不能。你可以通过扩展它们来克服这个问题:

    extension Character : CVarArg {
      func encode() -> Word[] {
        var result = Word[]()
        let s = String(self)
        for c in s.unicodeScalars {
          result.append(Word(c.value))
        }
        return result
      }
    }
    
    let c:Character = "c"
    
    ["I", c, 2*3] % "%@, %lc, %d"
    ==> "I, c, 6"
    

    我想知道是否有更简单的方法来编写encode(),但我还不确定。希望 Swift 将来会提供字符编码。但这里的教训是,可以为任意类型指定 encode 并进行格式化。

    class Car {
      let make = "Ford"
    }
    
    extension Car : CVarArg {
      func encode() -> Word[] {
        return NSString(string:self.make).encode()
      }
    }
    
    let car = Car()
    
    [car] % "%@"
    

    这里的教训是,您可以通过扩展将任意内容转换为 CVarArg 或任何协议。

    【讨论】:

    • 没错,这可能是多参数情况的最佳方法。整个事情的关键是找到 CVarArg :)
    【解决方案2】:

    找到了!

    @infix func % (value:CVarArg, format:String) -> String {
        return NSString(format:format, value)
    }
    

    这个单一的功能允许:

    5 % "%04x"
    
    3.4 % "%.3f"
    
    M_PI % "%.3f"
    
    Int64(32) % "%04X"
    

    不幸的是,它还允许:

    "String" % "%3.3s"
    

    并产生垃圾,但欢迎在没有参数类型检查的情况下使用 printf

    更进一步,通过定义一组函数:

    @infix func % (values:(CVarArg, CVarArg), format:String) -> String {
        return NSString(format:format, values.0, values.1)
    }
    
    @infix func % (values:(CVarArg, CVarArg, CVarArg), format:String) -> String {
        return NSString(format:format, values.0, values.1, values.2)
    }
    

    我们可以实现类似python的效果:

    (M_PI, 5) % "%.3f->%d"
    

    必须为每个元组长度定义一个有点难看,但我会继续研究它:)

    【讨论】:

      【解决方案3】:

      据我发现(与您经历了相同的旅程),泛型不是这里的解决方案,而是多重重载(这并不漂亮)-

      operator infix % { }
      @infix func % (format: String, value: Double) -> String {
          return NSString(format:format, value)
      }
      @infix func % (format: String, value: Float) -> String {
          return NSString(format:format, value)
      }
      @infix func % (format: String, value: Int) -> String {
          return NSString(format:format, value)
      }
      

      抱歉,与您的示例相反的参数顺序 - 允许

      println("PI = %.3f" % M_PI)
      

      【讨论】:

      • 是的,这就是我想要解决的问题。最终,我希望能够重现 (M_PI, M_PI_2) % "%.3f %.3f" 的 python 语法,但这可能期望太多:)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多