Попытка расширить IntegerType (и FloatingPointType); Почему нельзя преобразовать все типы Int в NSTimeInterval
(Это, вероятно, нуждается в лучшем названии...)
Я хотел бы иметь набор аксессоров, которые я могу использовать в коде, чтобы быстро выразить продолжительность времени. Например:
42.seconds
3.14.minutes
0.5.hours
13.days
Этот пост иллюстрирует, что вы не можете просто сделать это с простым новым протоколом, расширением и форсированием IntegerType
а также FloatingPointType
принять это. Так что я подумал, что я просто пойду по более избыточному маршруту и просто продолжу IntegerType
напрямую (а затем повторите код для FloatingPointType
).
extension IntegerType {
var seconds:NSTimeInterval {
return NSTimeInterval(self)
}
}
Сгенерированная ошибка сбивает с толку:
Playground execution failed: /var/folders/2k/6y8rslzn1m95gjpg534j7v8jzr03tz/T/./lldb/9325/playground131.swift:31:10: error: cannot invoke initializer for type 'NSTimeInterval' with an argument list of type '(Self)'
return NSTimeInterval(self)
^
/var/folders/2k/6y8rslzn1m95gjpg534j7v8jzr03tz/T/./lldb/9325/playground131.swift:31:10: note: overloads for 'NSTimeInterval' exist with these partially matching parameter lists: (Double), (UInt8), (Int8), (UInt16), (Int16), (UInt32), (Int32), (UInt64), (Int64), (UInt), (Int), (Float), (Float80), (String), (CGFloat), (NSNumber)
return NSTimeInterval(self)
Что меня смущает, так это то, что мне кажется, что я не могу выполнить инициализатор NSTimeInterval() с (Self), но все, что представляет собой Self, указано в следующей строке, где показаны все возможные инициализаторы NSTimeInterval. Что мне здесь не хватает?
Кроме того: я был бы рад, если бы было хорошо написанное руководство по системе типов Swift и тому подобное. Промежуточный / продвинутый материал не очень хорошо освещен в скудной документации Apple по Swift.
Обновление / уточнение:
То, что я хочу, чтобы иметь возможность оценить любое из приведенных выше выражений:
42.seconds --> 42
3.14.minutes --> 188.4
0.5.hours --> 1800
13.days --> 1123200
Кроме того, я хочу, чтобы тип возвращаемых данных был NSTimeInterval (псевдоним типа для Double), такой что:
42.seconds is NSTimeInterval --> true
3.14.minutes is NSTimeInterval --> true
0.5.hours is NSTimeInterval --> true
13.days is NSTimeInterval --> true
Я знаю, что могу добиться этого, просто расширяя Double
а также Int
в качестве таких:
extension Int {
var seconds:NSTimeInterval {
return NSTimeInterval(self)
}
var minutes:NSTimeInterval {
return NSTimeInterval(self * 60)
}
var hours:NSTimeInterval {
return NSTimeInterval(self * 3600)
}
var days:NSTimeInterval {
return NSTimeInterval(self * 3600 * 24)
}
}
extension Double {
var seconds:NSTimeInterval {
return NSTimeInterval(self)
}
var minutes:NSTimeInterval {
return NSTimeInterval(self * 60)
}
var hours:NSTimeInterval {
return NSTimeInterval(self * 3600)
}
var days:NSTimeInterval {
return NSTimeInterval(self * 3600 * 24)
}
}
Но я также хотел бы, чтобы сработало следующее выражение:
let foo:Uint = 4242
foo.minutes --> 254520
foo.minutes is NSTimeInterval --> true
Это не будет работать, хотя, потому что я только продлил Int
не UInt
, Я мог бы излишне продлить Uint
, а потом UInt16
, а потом Int16
, так далее....
Я хотел обобщить расширение Int
в IntegerType
как показано в исходном листинге, чтобы я мог просто получить преобразования в целом для всех целочисленных типов. И затем сделайте то же самое для FloatingPointType
а не конкретно Double
, Однако это приводит к первоначальной ошибке. Я хочу знать, почему я не могу расширить IntegerType, как показано на рисунке. Есть ли другие IntegerType
другие, чем те, которые указаны в списке, которые делают так, чтобы инициализатор NSTimeInterval() не разрешал?
3 ответа
Я мог бы избыточно расширить Uint, а затем UInt16, а затем Int16 и т.д....
Правильный. Вот как это делается сегодня в Свифте. Вы не можете объявить, что протокол соответствует другому протоколу в расширении. ("Почему?" "Потому что компилятор не позволяет".)
Но это не значит, что вам нужно переписать всю реализацию. В настоящее время вы должны реализовать это три раза (четыре раза, если хотите Float80
, но это не кажется полезным здесь). Сначала вы объявляете свой протокол.
import Foundation
// Declare the protocol
protocol TimeIntervalConvertible {
func toTimeInterval() -> NSTimeInterval
}
// Add all the helpers you wanted
extension TimeIntervalConvertible {
var seconds:NSTimeInterval {
return NSTimeInterval(self.toTimeInterval())
}
var minutes:NSTimeInterval {
return NSTimeInterval(self.toTimeInterval() * 60)
}
var hours:NSTimeInterval {
return NSTimeInterval(self.toTimeInterval() * 3600)
}
var days:NSTimeInterval {
return NSTimeInterval(self.toTimeInterval() * 3600 * 24)
}
}
// Provide the implementations. FloatingPointType doesn't have an equivalent to
// toIntMax(). There's no toDouble() or toFloatMax(). Converting a Float to
// a Double injects data noise in a way that converting Int8 to IntMax does not.
extension Double {
func toTimeInterval() -> NSTimeInterval { return NSTimeInterval(self) }
}
extension Float {
func toTimeInterval() -> NSTimeInterval { return NSTimeInterval(self) }
}
extension IntegerType {
func toTimeInterval() -> NSTimeInterval { return NSTimeInterval(self.toIntMax()) }
}
// And then we tell it that all the int types can get his implementation
extension Int: TimeIntervalConvertible {}
extension Int8: TimeIntervalConvertible {}
extension Int16: TimeIntervalConvertible {}
extension Int32: TimeIntervalConvertible {}
extension Int64: TimeIntervalConvertible {}
extension UInt: TimeIntervalConvertible {}
extension UInt8: TimeIntervalConvertible {}
extension UInt16: TimeIntervalConvertible {}
extension UInt32: TimeIntervalConvertible {}
extension UInt64: TimeIntervalConvertible {}
Вот как типы чисел в настоящее время делаются в Swift. Посмотри через stdlib. Вы увидите много таких вещей, как:
extension Double {
public init(_ v: UInt8)
public init(_ v: Int8)
public init(_ v: UInt16)
public init(_ v: Int16)
public init(_ v: UInt32)
public init(_ v: Int32)
public init(_ v: UInt64)
public init(_ v: Int64)
public init(_ v: UInt)
public init(_ v: Int)
}
Было бы хорошо в некоторых случаях говорить о "вещах, подобных числам"? Конечно. Вы не можете в Свифте сегодня.
"Зачем?"
Потому что компилятор не реализует это. Когда-нибудь это может. До тех пор, создавайте расширение для каждого типа, на котором вы хотите. Год назад это заняло бы еще больше кода.
Обратите внимание, что хотя некоторые из них "Swift еще не имеет этой функции", некоторые также специально. Swift намеренно требует явного преобразования между типами чисел. Преобразование между типами чисел часто может привести к потере информации или введению шума, и исторически было источником хитрых ошибок. Вы должны думать об этом каждый раз, когда вы конвертируете число. Например, существует очевидный случай, когда переход от Int64 к Int8 или от UInt8 к Int8 может привести к потере информации. Но переход с Int64 на Double также может привести к потере информации. Не все 64-битные целые числа могут быть выражены как Double. Это неуловимый факт, который довольно часто сжигает людей, когда имеешь дело с очень большими числами, и Свифт призывает тебя с этим справиться. Даже преобразование чисел с плавающей точкой в двойные вводит шум в ваши данные. 1/10, выраженная как число с плавающей запятой, отличается от значения 1/10, выраженного как значение типа Double. Когда вы конвертировали Float в Double, вы хотели расширить повторяющиеся цифры или нет? Вы будете вводить различные виды ошибок в зависимости от того, что вы выбираете, поэтому вам нужно выбрать.
Обратите внимание, что ваш .days
может вносить незначительные ошибки в зависимости от конкретной проблемной области. День не всегда 24 часа. Это может быть 23 часа или 25 часов в зависимости от изменений летнего времени. Иногда это имеет значение. Иногда это не так. Но это причина, чтобы быть очень осторожным с обработкой "дней", как если бы это было определенное количество секунд. Обычно, если вы хотите работать в течение нескольких дней, вы должны использовать NSDate
не NSTimeInterval
, Я бы очень подозрительно относился к этому конкретному.
Кстати, вас может заинтересовать моя старая реализация этой идеи. Вместо того, чтобы использовать синтаксис:
1.seconds
Я использовал синтаксис:
1 * Second
И затем перегруженное умножение, чтобы вернуть структуру, а не Double
, Возврат структуры таким способом дает намного лучшую безопасность типов. Например, я мог бы проверить тип "время * частота == циклы" и "циклы / время == частота", чего нельзя делать с Double. К сожалению, NSTimeInterval не является отдельным типом; это просто другое название для Double. Таким образом, любой метод, который вы используете для NSTimeInterval, применяется к каждому Double (что иногда странно).
Лично я бы, вероятно, решил всю эту проблему следующим образом:
let Second: NSTimeInterval = 1
let Seconds = Second
let Minute = 60 * Seconds
let Minutes = Minute
let Hour = 60 * Minutes
let Hours = Hour
let x = 100*Seconds
Вам даже не нужно перегружать операторов. Это уже сделано для вас.
NSTimeInterval
это просто псевдоним для Double
, Вы можете легко достичь того, что вы хотите, определив свою собственную структуру, например:
struct MyTimeInterval {
var totalSecs: NSTimeInterval
var totalHours: NSTimeInterval {
get {
return self.totalSecs / 3600.0
}
set {
self.totalSecs = newValue * 3600.0
}
}
init(_ secs: NSTimeInterval) {
self.totalSecs = secs
}
func totalHourString() -> String {
return String(format: "%.2f hours", arguments: [self.totalHours])
}
}
var t = MyTimeInterval(5400)
print(t.totalHourString())
extension Int {
var seconds: Int {
return self
}
var minutes: Int {
get {
return self * 60
}
}
var hours: Int {
get {
return minutes * 60
}
}
var timeString: String {
get {
let seconds = abs(self) % 60
let minutes = ((abs(self) - seconds) / 60) % 60
let hours = ((abs(self) - seconds) / 3660)
let sign = self < 0 ? "-" :" "
let str = String(format: "\(sign)%2d:%02d:%02d", arguments: [Int(hours),Int(minutes),Int(seconds)])
return str
}
}
}
6.minutes == 360.seconds // true
let t1 = 1.hours + 22.minutes + 12.seconds
let t2 = 12.hours - 15.minutes
let time = t1 - t2
print("t1 =",t1.timeString,t1) // t1 = 1:22:12 4932
print("t2 =",t2.timeString,t2) // t2 = 11:45:00 42300
print("t1-t2 =",time.timeString,time) // t1-t2 = -10:22:48 -37368
(12.hours / 30.minutes) == 24 // true