Warum brauchst du höhere Arten?

13

Einige Sprachen erlauben Klassen und Funktionen mit Typparametern (z. B. List<T>wo Tein 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.

GregRos
quelle
7
Hier gibt es mehrere gute Antworten: stackoverflow.com/questions/21170493/…
itsbruce
3
@itsbruce Insbesondere das FunctorBeispiel in Luis Casillas Antwort ist sehr intuitiv. Was tun List<T>und Option<T>gemeinsam haben? Wenn Sie mir eine oder eine Funktion T -> Sgeben, kann ich Ihnen eine List<S>oder geben Option<S>. Eine weitere Gemeinsamkeit ist, dass Sie versuchen können T, aus beiden einen Nutzen zu ziehen.
Doval
@Doval: Wie würdest du das erstere machen? In dem Maße, in dem man sich für Letzteres interessiert, würde ich denken, dass dies durch die Implementierung beider Typen gehandhabt werden könnte IReadableHolder<T>.
Supercat
@supercat Ich vermute , ich würde sie umsetzen müssen IMappable<K<_>, T>mit dem Verfahren K<S> Map(Func<T, S> f), wie die Umsetzung IMappable<Option<_>, T>, IMappable<List<_>, T>. Sie müssten sich also darauf beschränken K<T> : IMappable<K<_>, T>, irgendeinen Nutzen daraus zu ziehen.
GregRos
1
Casting ist keine angemessene Analogie. Mit Typklassen (oder ähnlichen Konstrukten) wird gezeigt, wie ein gegebener Typ den höherwertigen Typ erfüllt. Dies beinhaltet normalerweise das Definieren neuer Funktionen oder das Zeigen, welche vorhandenen Funktionen (oder Methoden, wenn es sich um eine OO-Sprache wie Scala handelt) verwendet werden können. Sobald dies geschehen ist, funktionieren alle Funktionen, die für den höheren Typ definiert wurden, mit diesem Typ. Es ist jedoch viel mehr als nur eine Schnittstelle, da mehr als nur ein einfacher Satz von Funktions- / Methodensignaturen definiert ist. Ich muss wohl eine Antwort schreiben, um zu zeigen, wie das funktioniert.
Itsbruce

Antworten:

5

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 von TIhrer 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. Nur List<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<>und List<>als "abbildbar" in dem Sinne, dass Sie, wenn Sie eine Funktion haben, eine Option<T>in eine Option<S>oder eine List<T>in eine umwandeln könnten List<S>. Denken Sie daran, dass Klassen nicht verwendet werden können, um über höherwertige Typen direkt zu abstrahieren. Stattdessen erstellen wir eine Schnittstelle:

IMappable<K<_>, T> where K<T> : IMappable<K<_>, T>

Und dann implementieren wir die Schnittstelle in beide List<T>und Option<T>als IMappable<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) Typen Option<T>und zu setzen List<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,

class Mappable mp where
    map :: mp a -> mp b

Hierbei handelt es sich um eine Einschränkung, die direkt auf einen (nicht angegebenen) Typ mpangewendet wird, der einen Typparameter annimmt, und erfordert, dass dieser mit der Funktion verknüpft ist, die aus mapeinem mp<a>in einen verwandeltmp<b> . Wir können dann Funktionen schreiben, die höherwertige Typen einschränken, indem Sie Mappablegenau 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.

GregRos
quelle
War zu beschäftigt damit, den Job zu wechseln, werde aber endlich Zeit finden, meine eigene Antwort zu schreiben. Ich denke jedoch, dass Sie von der Idee der Einschränkungen abgelenkt wurden. Ein wesentlicher Aspekt von höherwertigen Typen ist, dass sie die Definition von Funktionen / Verhalten ermöglichen, die auf jeden Typ angewendet werden können, dessen Qualifizierung logischerweise gezeigt werden kann. Typklassen (eine Anwendung mit höherwertigen Typen) legen keine Einschränkungen für vorhandene Typen fest, sondern fügen ihnen Verhalten hinzu.
itsbruce
Ich denke, es ist eine Frage der Semantik. Eine Typklasse definiert eine Typklasse. Wenn Sie eine Funktion wie definieren (Mappable mp) => mp a -> mp b, haben Sie eine Einschränkung festgelegt mp, um Mitglied der Typklasse zu sein Mappable. Wenn Sie einen Typ als Optioneine Instanz von deklarieren Mappable, 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.
GregRos
Außerdem bin ich mir ziemlich sicher, dass Typklassen nicht direkt mit höherwertigen Typen zusammenhängen. In Scala gibt es Typen höherer Klassen, jedoch keine Typklassen, und Sie können Typklassen auf Typen mit dieser Art beschränken, *ohne sie unbrauchbar zu machen. Es ist jedoch definitiv richtig, dass Typklassen sehr leistungsfähig sind, wenn Sie mit höherwertigen Typen arbeiten.
GregRos
Scala hat Typklassen. Sie werden implizit implementiert. Die Implizite stellen das Äquivalent der Haskell-Typklasseninstanzen bereit. Es ist eine fragilere Implementierung als die von Haskell, da es keine dedizierte Syntax dafür gibt. Aber es war immer eines der Hauptziele von Scala und sie machen den Job.
Itsbruce