Ihre *
Implementierung hat ein paar subtile Probleme. Hier ist die Implementierung Sie nach:
static func *(lhs: Float, rhs: ThingBox<T>) -> Self {
return self.init(thing: lhs * rhs.thing)
}
Zuerst Sie nicht Self
als Parametertyp verwenden können. Du musst explizit sein. Self
bedeutet "der tatsächliche Typ" und wenn Sie das für eine Unterklasse verwenden könnten, würde dies LSP verletzen. Angenommen, ich habe die Typen Animal
und Dog
(mit den offensichtlichen Beziehungen). Sagen, dass ich die Funktion schrieb:
class Animal {
func f(_ a: Self) { ... }
}
mit der Bedeutung, dass Animal.f
Animal
nehmen, aber Dog.f
nur Dog
nehmen aber nicht eine Cat
nehmen. So würden Sie folgendes erwarten:
dog.f(otherDog) // this works
dog.f(cat) // this fails
Aber das ist die Regeln der Substitution bricht. Was passiert, wenn ich dies schreibe:
let animal: Animal = Dog()
animal.f(cat)
Das sollte legal sein, weil Animal.f
jeden Animal
nehmen, aber die Umsetzung von Dog.f
kann eine Katze nicht nehmen. Typenkonflikt Boom. Es ist also nicht legal. (Diese Einschränkung existiert nicht für Rückgabetypen. Ich überlasse das dem Leser als Übung. Versuchen Sie, ein Beispiel wie das obige zu erstellen.
Der zweite Fehler war nur ein Syntaxfehler. Es ist nicht Self()
, es ist self.init()
. In einer statischen Methode ist self
der dynamische Typ (was Sie wollen), und Swift erfordert, dass Sie explizit init
aufrufen, wenn Sie auf diese Weise verwendet werden. Das ist nur Syntax, kein tiefes Problem wie das andere.
In Swift gibt es keine Möglichkeit, die Sache zu erben, von der Sie sprechen. Wenn Sie zu überlasten wollen, ist das in Ordnung, aber Sie müssen über die Typen explizit sein:
class OtherBox: ThingBox<Int> {
static func *(lhs: Float, rhs: OtherBox) -> Self {
return self.init(thing: lhs * rhs.thing)
}
}
Dies macht genau das, was Sie wollen, aber es muss jedes Kind hinzugefügt werden; Es erbt nicht automatisch mit Kovarianz. Swift hat kein starkes Kovarianzsystem, um es auszudrücken.
Das heißt, wenn Sie anfangen, Generika, Protokolle und Unterklassen auf diese Weise zu vermischen, werden Sie in viele, viele seltsame Fälle geraten, sowohl aufgrund der Mathematik, als auch aufgrund der aktuellen Swift-Einschränkungen.Sie sollten sorgfältig fragen, ob Ihr tatsächlicher Code so viel Parametrisierung benötigt. Die meisten Fälle, in denen ich auf diese Art von Fragen stoße, sind "für den Fall, dass wir es brauchen" überdimensioniert und vereinfacht nur Ihre Typen und konkretisiert alles, was Sie brauchen, um das eigentliche Programm zu lösen, das Sie schreiben möchten. Es ist nicht so, dass es nicht schön wäre, unglaublich generische Algorithmen zu entwickeln, die auf höherklassigen Typen basieren, aber Swift ist einfach nicht die Sprache dafür (und möglicherweise nie; es gibt viele Kosten beim Hinzufügen dieser Funktionen).
Aber dies bewirkt, dass alle Aufrufe von '*' den Ergebnistyp 'ThingBox' haben. Wenn ich '*' verwende, um eine Unterklasse von 'ThingBox' zu multiplizieren (sagen wir' Klasse OtherBox: ThingBox {...} '), ist das Ergebnis eine' ThingBox' und keine 'OtherBox', die den Typen zu widersprechen scheint im Protokoll angegeben. –
dented42