swift - protocol - Intentando extender IntegerType(y FloatingPointType); ¿Por qué no se pueden convertir todos los tipos de Int en NSTimeInterval?
swift associated type (3)
(Esto probablemente necesita un mejor título ...)
Me gustaría tener un conjunto de accesores que pueda usar en el código para expresar rápidamente las duraciones de tiempo. P.ej:
42.seconds
3.14.minutes
0.5.hours
13.days
Esta publicación muestra que no se puede simplemente hacer con un nuevo protocolo simple, extensión y forzar IntegerType
y FloatingPointType
para adoptar eso. Así que pensé que iría por la ruta más redundante y simplemente extendería IntegerType
directamente (y luego repetiría el código para FloatingPointType
).
extension IntegerType {
var seconds:NSTimeInterval {
return NSTimeInterval(self)
}
}
El error generado es confuso:
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)
Lo que me confunde es que parece decir que no puedo hacer un inicializador NSTimeInterval () con (Self), pero todo lo que Self representa aparece en la siguiente línea donde muestra todos los inicializadores posibles de NSTimeInterval. ¿Que me estoy perdiendo aqui?
Aparte: me encantaría que hubiera un tutorial bien escrito sobre el sistema de tipos de Swift y hacer este tipo de cosas. El material intermedio / avanzado simplemente no está bien cubierto en la escasa documentación de Swift de Apple
Actualización / aclaración :
Lo que quiero es poder evaluar cualquiera de las expresiones anteriores:
42.seconds --> 42
3.14.minutes --> 188.4
0.5.hours --> 1800
13.days --> 1123200
Además, quiero que el tipo de devolución de estos sea NSTimeInterval (tipo de alias para el doble), de modo que:
42.seconds is NSTimeInterval --> true
3.14.minutes is NSTimeInterval --> true
0.5.hours is NSTimeInterval --> true
13.days is NSTimeInterval --> true
Sé que puedo lograr esto simplemente extendiendo Double
e Int
como tal:
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)
}
}
Pero también me gustaría que la siguiente expresión funcione:
let foo:Uint = 4242
foo.minutes --> 254520
foo.minutes is NSTimeInterval --> true
Sin embargo, esto no funcionará porque solo he extendido Int
, no UInt
. Podría extender redundantemente Uint
, y luego UInt16
, y luego Int16
, etc.
Quería generalizar la extensión de Int
a IntegerType
como se muestra en la lista original, de modo que solo podría obtener las conversiones generalmente para todos los tipos de enteros. Y luego haga lo mismo para FloatingPointType
lugar de específicamente Double
. Sin embargo, eso produce el error original. Quiero saber por qué no puedo extender IntegerType como se muestra generalmente. ¿Hay otros adoptadores IntegerType
distintos de los que se muestran en la lista, que hacen que el inicializador NSTimeInterval () no se resuelva?
NSTimeInterval
es solo un alias para Double
. Puedes lograr fácilmente lo que quieres definiendo tu propia estructura, así:
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
Podría extender redundantemente Uint, y luego UInt16, y luego Int16, etc.
Correcto. Así es como se hace hoy en Swift. No puede declarar que un protocolo se ajusta a otro protocolo en una extensión. ("¿Por qué?" "Porque el compilador no lo permite").
Pero eso no significa que deba volver a escribir toda la implementación. Actualmente tiene que implementarlo tres veces (cuatro veces si quiere Float80
, pero eso no parece útil aquí). Primero, declaras tu protocolo.
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 {}
Así es como los tipos de números se hacen actualmente en Swift. Mire a través de stdlib. Verás muchas cosas como:
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)
}
¿Sería bueno en algunos casos hablar de "cosas tipo números"? Por supuesto. No puedes en Swift hoy.
"¿Por qué?"
Porque el compilador no lo implementa. Algún día puede. Hasta entonces, crea la extensión para cada tipo que desees. Hace un año esto habría tomado aún más código.
Tenga en cuenta que si bien algo de esto es "Swift aún no tiene esa característica", algunos también son a propósito. Swift requiere intencionalmente una conversión explícita entre los tipos de números. La conversión entre tipos de números a menudo puede llevar a perder información o inyectar ruido, e históricamente ha sido una fuente de errores complicados. Deberías pensar en eso cada vez que conviertas un número. Por ejemplo, está el caso obvio de que pasar de Int64 a Int8 o de UInt8 a Int8 puede perder información. Pero pasar de Int64 a Double también puede perder información. No todos los enteros de 64 bits se pueden expresar como un doble. Este es un hecho sutil que quema a las personas con bastante frecuencia cuando se trata de números muy grandes, y Swift lo alienta a enfrentarlo. Incluso la conversión de un Float a Double inyecta ruido en sus datos. 1/10 expresado como Float es un valor diferente de 1/10 expresado como Double. Cuando convierte el flotador a un doble, ¿quiso extender los dígitos repetidos o no? Introducirá diferentes tipos de error dependiendo de cuál elija, por lo que debe elegir.
Tenga en cuenta también que sus .days
pueden introducir errores sutiles dependiendo del dominio del problema exacto. Un día no siempre es 24 horas. Pueden ser 23 horas o 25 horas dependiendo de los cambios de horario de verano. A veces eso importa. A veces no es así. Pero es una razón para tener mucho cuidado al tratar los "días" como si fuera una cantidad específica de segundos. Por lo general, si desea trabajar en días, debe usar NSDate
, no NSTimeInterval
. Sería muy sospechoso de ese en particular.
Por cierto, puede que le interese mi antigua implementación de esta idea . En lugar de usar la sintaxis:
1.seconds
Usé la sintaxis:
1 * Second
Y luego sobrecarga la multiplicación para devolver una estructura en lugar de un Double
. Devolver una estructura de esta manera proporciona una seguridad de tipo mucho mejor. Por ejemplo, podría tipear-verificar "tiempo * frecuencia == ciclos" y "ciclos / tiempo == frecuencia", que es algo que no se puede hacer con Doble. Desafortunadamente NSTimeInterval no es un tipo separado; es solo otro nombre para Double. Entonces, cualquier método que pongas en NSTimeInterval se aplica a cada Doble (lo que a veces es extraño).
Personalmente, probablemente resolvería todo este problema de esta manera:
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
Ni siquiera necesita sobrecargar a los operadores. Ya está hecho para ti.