Mir ist bewusst, dass das Konzept der Invarianten über mehrere Programmierparadigmen hinweg existiert. Beispielsweise sind Schleifeninvarianten für die OO-, funktionale und prozedurale Programmierung relevant.
Eine sehr nützliche Art in OOP ist jedoch eine Invariante der Daten eines bestimmten Typs. Dies nenne ich im Titel "typbasierte Invarianten". Zum Beispiel Fraction
könnte ein Typ ein numerator
und haben denominator
, mit der Invariante, dass sein gcd immer 1 ist (dh der Bruch ist in einer reduzierten Form). Ich kann dies nur garantieren, indem ich eine Art Kapselung des Typs habe und seine Daten nicht frei einstellen kann. Im Gegenzug muss ich nie prüfen, ob es reduziert ist, damit ich Algorithmen wie Gleichheitsprüfungen vereinfachen kann.
Wenn ich dagegen einfach einen Fraction
Typ deklariere, ohne diese Garantie durch Kapselung bereitzustellen, kann ich keine Funktionen für diesen Typ sicher schreiben, die davon ausgehen, dass der Bruch reduziert wird, da in Zukunft jemand anderes mitkommen und einen Weg hinzufügen könnte einen nicht reduzierten Bruchteil zu bekommen.
Im Allgemeinen kann das Fehlen dieser Art von Invariante zu Folgendem führen:
- Komplexere Algorithmen als Vorbedingungen müssen an mehreren Stellen überprüft / sichergestellt werden
- DRY-Verstöße, da diese wiederholten Voraussetzungen dasselbe zugrunde liegende Wissen darstellen (dass die Invariante wahr sein sollte)
- Voraussetzungen müssen durch Laufzeitfehler und nicht durch Garantien zur Kompilierungszeit erzwungen werden
Meine Frage ist also, wie die funktionale Programmierantwort auf diese Art von Invariante lautet. Gibt es eine funktional-idiomatische Möglichkeit, mehr oder weniger dasselbe zu erreichen? Oder gibt es einen Aspekt der funktionalen Programmierung, der die Vorteile weniger relevant macht?
quelle
PrimeNumber
Klasse. Es wäre zu teuer, für jede Operation mehrere redundante Überprüfungen der Primalität durchzuführen, aber es ist keine Art von Test, der zur Kompilierungszeit durchgeführt werden kann. (Viele Operationen, die Sie mit Primzahlen ausführen möchten, z. B. Multiplikation, bilden keinen Abschluss , dh die Ergebnisse sind wahrscheinlich keine garantierte Primzahl. (Als KommentarAntworten:
Einige funktionale Sprachen wie OCaml verfügen über integrierte Mechanismen zur Implementierung abstrakter Datentypen, wodurch einige Invarianten erzwungen werden . Sprachen, die nicht über solche Mechanismen verfügen, sind darauf angewiesen, dass der Benutzer „nicht unter den Teppich schaut“, um die Invarianten durchzusetzen.
Abstrakte Datentypen in OCaml
In OCaml werden Module zum Strukturieren eines Programms verwendet. Ein Modul hat eine Implementierung und eine Signatur , wobei letzteres eine Art Zusammenfassung der im Modul definierten Werte und Typen ist, während das erstere die tatsächlichen Definitionen bereitstellt. Dies kann lose mit dem Diptychon verglichen werden, das
.c/.h
C-Programmierern bekannt ist.Als Beispiel können wir das
Fraction
Modul folgendermaßen implementieren :Diese Definition kann jetzt folgendermaßen verwendet werden:
Jeder kann Werte des Typanteils direkt unter Umgehung des eingebauten Sicherheitsnetzes erzeugen
Fraction.make
:Um dies zu verhindern, ist es möglich, die konkrete Definition des Typs
Fraction.t
wie folgt auszublenden :Die einzige Möglichkeit, eine zu erstellen,
AbstractFraction.t
besteht darin, dieAbstractFraction.make
Funktion zu verwenden.Abstrakte Datentypen im Schema
Die Schemasprache verfügt nicht über denselben Mechanismus für abstrakte Datentypen wie OCaml. Es beruht darauf, dass der Benutzer „nicht unter den Teppich schaut“, um eine Kapselung zu erreichen.
In Schema ist es üblich, Prädikate wie das
fraction?
Erkennen von Werten zu definieren , die die Möglichkeit bieten, die Eingabe zu validieren. Nach meiner Erfahrung besteht die vorherrschende Verwendung darin, den Benutzer seine Eingabe validieren zu lassen, wenn er einen Wert fälscht, anstatt die Eingabe in jedem Bibliotheksaufruf zu validieren.Es gibt jedoch verschiedene Strategien, um die Abstraktion zurückgegebener Werte zu erzwingen, z. B. die Rückgabe eines Abschlusses, der den Wert liefert, wenn er angewendet wird, oder die Rückgabe eines Verweises auf einen Wert in einem von der Bibliothek verwalteten Pool - aber ich habe in der Praxis keine davon gesehen.
quelle
Die Kapselung ist keine Funktion, die mit OOP geliefert wurde. Jede Sprache, die eine ordnungsgemäße Modularisierung unterstützt, hat sie.
Hier ist ungefähr, wie Sie es in Haskell machen:
Um ein Rational zu erstellen, verwenden Sie jetzt die Verhältnisfunktion, die die Invariante erzwingt. Da Daten unveränderlich sind, können Sie die Invariante später nicht mehr verletzen.
Dies kostet Sie jedoch etwas: Es ist dem Benutzer nicht mehr möglich, dieselbe Dekonstruktionsdeklaration wie Nenner und Zähler zu verwenden.
quelle
Gehen Sie genauso vor: Erstellen Sie einen Konstruktor , der die Einschränkung erzwingt, und stimmen Sie zu, diesen Konstruktor zu verwenden, wenn Sie einen neuen Wert erstellen.
Aber Karl, in OOP muss man nicht zustimmen , den Konstruktor zu verwenden. Ja wirklich?
Tatsächlich sind die Möglichkeiten für diese Art von Missbrauch in FP geringer. Sie müssen den Konstruktor wegen der Unveränderlichkeit zuletzt setzen. Ich wünschte, die Leute würden aufhören, die Kapselung als einen Schutz vor inkompetenten Kollegen zu betrachten oder die Notwendigkeit der Kommunikation von Einschränkungen zu vermeiden. Das macht es nicht. Es begrenzt nur die Orte, die Sie überprüfen müssen. Gute FP-Programmierer verwenden auch die Kapselung. Es kommt nur in Form der Kommunikation einiger bevorzugter Funktionen, um bestimmte Arten von Modifikationen vorzunehmen.
quelle