Общий протокол для базовых типов

Я размышлял о возможных реализациях форматирования строк для 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

Достаточно разумный, я мог бы проходить в кортеже, или что-то в равной степени не objective-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:...)?

Ответ 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 или в любой протокол через расширения.

Ответ 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)
}

Мы можем достичь эффектов, подобных питону:

(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)