Ich habe mit Vektoren und Matrizen gespielt, wo die Größe in ihrem Typ codiert ist, mit der neuen DataKinds
Erweiterung. Es geht im Grunde wie folgt:Rekursiv definierte Instanzen und Einschränkungen
data Nat = Zero | Succ Nat
data Vector :: Nat -> * -> * where
VNil :: Vector Zero a
VCons :: a -> Vector n a -> Vector (Succ n) a
Jetzt sind wir typische Beispiele wie Functor
und Applicative
wollen. Functor
ist einfach:
instance Functor (Vector n) where
fmap f VNil = VNil
fmap f (VCons a v) = VCons (f a) (fmap f v)
Aber mit dem Applicative
Beispiel gibt es ein Problem: Wir wissen nicht, welche Art in reinen zurückzukehren. Wir können jedoch die Instanz induktiv auf die Größe des Vektors definieren:
instance Applicative (Vector Zero) where
pure = const VNil
VNil <*> VNil = VNil
instance Applicative (Vector n) => Applicative (Vector (Succ n)) where
pure a = VCons a (pure a)
VCons f fv <*> VCons a v = VCons (f a) (fv <*> v)
Doch obwohl diese Instanz für alle Vektoren gilt, wird die Typprüfer nicht wissen, so müssen wir die Applicative
Einschränkung tragen Jedes Mal, wenn wir die Instanz verwenden.
Nun, wenn dies nur für die Applicative
-Instanz gilt, wäre es kein Problem, aber es stellt sich heraus, dass der Trick der rekursiven Instanz Deklarationen wesentlich ist, wenn mit solchen Typen zu programmieren. Zum Beispiel, wenn wir eine Matrix als ein Vektor von Zeilenvektoren unter Verwendung der Bibliothek TypeCompose definieren
type Matrix nx ny a = (Vector nx :. Vector ny) a
haben wir eine Typ-Klasse zu definieren und rekursive Instanz Erklärungen fügen sowohl die Transponierte und Matrixmultiplikation zu implementieren. Dies führt zu einer enormen Zunahme von Beschränkungen, die wir bei jeder Verwendung des Codes mit sich herumtragen müssen, obwohl die Instanzen tatsächlich für alle Vektoren und Matrizen gelten (wodurch die Beschränkungen nutzlos werden).
Gibt es eine Möglichkeit, all diese Einschränkungen zu vermeiden? Wäre es möglich, den Typenprüfer so zu erweitern, dass er solche induktiven Konstruktionen erkennen kann?
Nach ein paar Lektüren und ein paar Experimenten, habe ich endlich diese Antwort verstanden. Im Grunde können Sie mit der HasNatty-Einschränkung eine Rekursion auf der Werteebene statt auf der Typebene durchführen, wodurch zusätzliche Einschränkungen vermieden werden. Das hilft sehr. Allerdings habe ich wirklich zu kämpfen, um zu sehen, wie Matrix-Multiplikation in Bezug auf Traversable implementieren. Könnten Sie ein paar Hinweise geben? Die meisten Implementierungen der Matrixmultiplikation transponieren zuerst eine der Matrizen. Kannst du die Matrix mit Traversable transponieren? – Almanildo
Ja.Wenn Sie die obigen Instanzen für "Applicative" und "Traversable" haben, ist "transpose" "traverse id". Um dies zu sehen, überprüfen Sie zuerst die Typen. 'traverse :: Applicative vm => (s -> vm t) -> Vektor ns -> vm (Vector nt)' und jetzt nehmen wir 'vm = Vector m' und' s = Vector mt', wir erhalten 'traverse id: : Vektor n (Vektor mt) -> Vektor m (Vektor nt) '. Operativ nimmt 'traid id'' VNil' zu einem Vektor von 'VNil' und vektorisiert -VCons' in die obere Reihe und die Transponierte des Rests, wobei die oberste Reihe in die Spalte ganz links gesetzt wird. – pigworker
Richtig, aber das benötigt immer noch die HasNatty-Einschränkung für den inneren Vektor. Trotzdem nett, danke. – Almanildo