Einige Sprachen erlauben Klassen und Funktionen mit Typparametern (z. B. List<T>
wo T
ein beliebiger Typ sein kann). Zum Beispiel können Sie eine Funktion haben wie:
List<S> Function<S, T>(List<T> list)
In einigen Sprachen kann dieses Konzept jedoch um eine Stufe höher erweitert werden, sodass Sie eine Funktion mit der Signatur haben:
K<S> Function<K<_>, S, T>(K<T> arg)
Wobei K<_>
selbst ein solcher List<_>
Typ einen Typparameter hat. Dieser "partielle Typ" ist als Typkonstruktor bekannt.
Meine Frage ist, warum brauchst du diese Fähigkeit? Es ist sinnvoll, einen Typ wie zu haben, List<T>
weil alle List<T>
fast genau gleich sind, aber alle K<_>
völlig unterschiedlich sein können. Sie können eine Option<_>
und eine haben List<_>
, die überhaupt keine gemeinsame Funktionalität haben.
Functor
Beispiel in Luis Casillas Antwort ist sehr intuitiv. Was tunList<T>
undOption<T>
gemeinsam haben? Wenn Sie mir eine oder eine FunktionT -> S
geben, kann ich Ihnen eineList<S>
oder gebenOption<S>
. Eine weitere Gemeinsamkeit ist, dass Sie versuchen könnenT
, aus beiden einen Nutzen zu ziehen.IReadableHolder<T>
.IMappable<K<_>, T>
mit dem VerfahrenK<S> Map(Func<T, S> f)
, wie die UmsetzungIMappable<Option<_>, T>
,IMappable<List<_>, T>
. Sie müssten sich also darauf beschränkenK<T> : IMappable<K<_>, T>
, irgendeinen Nutzen daraus zu ziehen.Antworten:
Da niemand sonst die Frage beantwortet hat, denke ich, dass ich es selbst versuchen werde. Ich muss ein bisschen philosophisch werden.
Bei der generischen Programmierung geht es darum, ähnliche Typen zu abstrahieren, ohne dass Typinformationen verloren gehen (was beim objektorientierten Wertepolymorphismus der Fall ist). Zu diesem Zweck müssen die Typen notwendigerweise eine bestimmte Art von Schnittstelle (eine Reihe von Operationen, nicht den OO-Begriff) gemeinsam verwenden, die Sie verwenden können.
In objektorientierten Sprachen erfüllen Typen eine Schnittstelle durch Klassen. Jede Klasse hat eine eigene Schnittstelle, die als Teil ihres Typs definiert ist. Da alle Klassen
List<T>
dieselbe Schnittstelle verwenden, können Sie Code schreiben, der unabhängig vonT
Ihrer Auswahl funktioniert . Eine andere Möglichkeit, eine Schnittstelle zu erzwingen, ist eine Vererbungsbeschränkung, und obwohl die beiden unterschiedlich zu sein scheinen, ähneln sie sich, wenn Sie darüber nachdenken.In den meisten objektorientierten Sprachen
List<>
ist es kein richtiger Typ für sich. Es hat keine Methoden und somit keine Schnittstelle. NurList<T>
das hat diese Dinge. Technisch gesehen sind die einzigen Typen, über die Sie sinnvoll abstrahieren können, die mit der Art*
. Um höherwertige Typen in einer objektorientierten Welt zu verwenden, müssen Sie Typeinschränkungen in einer Weise formulieren, die mit dieser Einschränkung vereinbar ist.Zum Beispiel können wir, wie in den Kommentaren erwähnt, anzeigen
Option<>
undList<>
als "abbildbar" in dem Sinne, dass Sie, wenn Sie eine Funktion haben, eineOption<T>
in eineOption<S>
oder eineList<T>
in eine umwandeln könntenList<S>
. Denken Sie daran, dass Klassen nicht verwendet werden können, um über höherwertige Typen direkt zu abstrahieren. Stattdessen erstellen wir eine Schnittstelle:Und dann implementieren wir die Schnittstelle in beide
List<T>
undOption<T>
alsIMappable<List<_>, T>
undIMappable<Option<_>, T>
jeweils. Was wir getan haben, ist die Verwendung höherwertiger Typen, um Einschränkungen für die tatsächlichen (nicht höherwertigen) TypenOption<T>
und zu setzenList<T>
. So wird es in Scala gemacht, obwohl Scala natürlich Funktionen wie Eigenschaften, Typvariablen und implizite Parameter hat, die es aussagekräftiger machen.In anderen Sprachen ist es möglich, über höherwertige Typen direkt zu abstrahieren. In Haskell, einer der höchsten Autoritäten für Typsysteme, können wir eine Typklasse für jeden Typ formulieren, auch wenn er eine höhere Art hat. Beispielsweise,
Hierbei handelt es sich um eine Einschränkung, die direkt auf einen (nicht angegebenen) Typ
mp
angewendet wird, der einen Typparameter annimmt, und erfordert, dass dieser mit der Funktion verknüpft ist, die ausmap
einemmp<a>
in einen verwandeltmp<b>
. Wir können dann Funktionen schreiben, die höherwertige Typen einschränken, indem SieMappable
genau wie in objektorientierten Sprachen eine Vererbungsbeschränkung platzieren. Naja, so ungefähr.Zusammenfassend gesagt hängt Ihre Fähigkeit, höherwertige Typen zu verwenden, von Ihrer Fähigkeit ab, sie zu beschränken oder als Teil von Typbeschränkungen zu verwenden.
quelle
(Mappable mp) => mp a -> mp b
, haben Sie eine Einschränkung festgelegtmp
, um Mitglied der Typklasse zu seinMappable
. Wenn Sie einen Typ alsOption
eine Instanz von deklarierenMappable
, fügen Sie diesem Typ Verhalten hinzu. Ich vermute, Sie könnten dieses Verhalten lokal anwenden, ohne jemals einen Typ einzuschränken, aber dann unterscheidet es sich nicht von der Definition einer normalen Funktion.*
ohne sie unbrauchbar zu machen. Es ist jedoch definitiv richtig, dass Typklassen sehr leistungsfähig sind, wenn Sie mit höherwertigen Typen arbeiten.