Попытка расширить 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() не разрешается?


person Travis Griggs    schedule 10.11.2015    source источник


Ответы (2)


Я мог бы избыточно расширить Uint, а затем UInt16, а затем Int16 и т. д....

Правильный. Вот как это делается сегодня в Swift. Вы не можете объявить, что протокол соответствует другому протоколу в расширении. («Почему?» «Потому что компилятор этого не позволяет».)

Но это не значит, что вам нужно переписать всю реализацию. В настоящее время вам нужно реализовать его три раза (четыре раза, если вы хотите 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 еще не имеет этой функции", некоторые также преднамеренно. Swift намеренно требует явного преобразования между числовыми типами. Преобразование между числовыми типами часто может приводить к потере информации или введению шума и исторически было источником коварных ошибок. Вы должны думать об этом каждый раз, когда конвертируете число. Например, есть очевидный случай, когда переход от Int64 к Int8 или от UInt8 к Int8 может привести к потере информации. Но переход от Int64 к Double также может привести к потере информации. Не все 64-битные целые числа могут быть выражены как Double. Это тонкий факт, который довольно часто обжигает людей при работе с очень большими числами, и Swift призывает вас с этим смириться. Даже преобразование Float в Double вводит шум в ваши данные. 1/10, выраженная как Float, отличается от 1/10, выраженной как Double. Когда вы конвертируете Float в Double, вы хотели расширить повторяющиеся цифры или нет? Вы будете вводить различные виды ошибок в зависимости от того, что вы выберете, поэтому вам нужно выбрать.

Также обратите внимание, что ваш .days может содержать небольшие ошибки в зависимости от конкретной проблемной области. В сутках не всегда 24 часа. Это может быть 23 часа или 25 часов в зависимости от изменения летнего времени. Иногда это имеет значение. Иногда это не так. Но это причина быть очень осторожным при обращении с «днями», как если бы это было определенное количество секунд. Обычно, если вы хотите работать в днях, вы должны использовать NSDate, а не NSTimeInterval. Я бы очень подозрительно отнесся к этому конкретному.

Кстати, вас может заинтересовать моя старая реализация этой идеи. Вместо использования синтаксиса:

1.seconds

Я использовал синтаксис:

1 * Second

А затем перегруженное умножение, возвращающее структуру, а не Double. Возврат структуры таким образом обеспечивает гораздо лучшую безопасность типов. Например, я мог бы ввести «время * частота == циклы» и «циклы / время == частота», чего нельзя сделать с Double. К сожалению, NSTimeInterval не является отдельным типом; это просто другое название Double. Таким образом, любой метод, который вы добавляете в NSTimeInterval, применяется к каждому двойнику (что иногда странно).

Лично я, вероятно, решил бы всю эту проблему следующим образом:

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

Вам даже не нужно перегружать операторы. Это уже сделано за вас.

person Rob Napier    schedule 11.11.2015

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())
person Code Different    schedule 10.11.2015
comment
Но это не отвечает на мой вопрос... Это не позволяет мне вычислять/извлекать продолжительность из чисел в стандартном стиле object-receiver.method. И я далеко не понимаю, как сделать простое расширение для числовых протоколов в Swift. - person Travis Griggs; 10.11.2015
comment
Пожалуйста, отредактируйте свой вопрос, чтобы показать, что вы хотите сделать. Как бы то ни было, вы можете установить t.totalHours = 2, а totalSeconds будет автоматически обновлено до 7200. Если вы расширите Double, вы предполагаете, что любое число является значением времени. - person Code Different; 10.11.2015