Ich komme aus einem objektorientierten Hintergrund, in dem ich gelernt habe, dass Klassen verwendet werden oder zumindest verwendet werden können, um eine Abstraktionsebene zu erstellen, die ein einfaches Recycling von Code ermöglicht, der dann entweder zum Erstellen von Objekten oder zur Vererbung verwendet werden kann.
Wie zum Beispiel kann ich eine Tierklasse haben und dann Katzen und Hunde erben und so, dass alle viele der gleichen Merkmale erben, und aus diesen Unterklassen kann ich dann Objekte machen, die eine Tierrasse oder sogar den Namen spezifizieren können davon.
Oder ich kann Klassen verwenden, um mehrere Instanzen desselben Codes anzugeben, der leicht unterschiedliche Dinge behandelt oder enthält. wie Knoten in einem Suchbaum oder mehrere verschiedene Datenbankverbindungen und was nicht.
Ich bin vor kurzem zur funktionalen Programmierung übergegangen und begann mich zu fragen:
Wie gehen rein funktionale Sprachen mit solchen Dingen um? Das heißt, Sprachen ohne Konzept von Klassen und Objekten.
quelle
Antworten:
Viele Funktionssprachen haben ein Modulsystem. (Übrigens auch viele objektorientierte Sprachen.) Aber auch ohne eine können Sie Funktionen als Module verwenden.
JavaScript ist ein gutes Beispiel. In JavaScript werden Funktionen sowohl zur Implementierung von Modulen als auch zur objektorientierten Kapselung verwendet. In Schema, das die Hauptinspiration für JavaScript war, gibt es nur Funktionen. Funktionen werden verwendet, um fast alles zu implementieren: Objekte, Module ( im Racket Einheiten genannt ), sogar Datenstrukturen.
OTOH, Haskell und die ML-Familie haben ein explizites Modulsystem.
Bei der Objektorientierung geht es um Datenabstraktion. Das ist es. Modularität, Vererbung, Polymorphismus und sogar veränderlicher Zustand sind orthogonale Bedenken.
quelle
module
. Ich finde es bedauerlich, dass Racket ein Konzept hat,module
das nicht als Modul bezeichnet wird, und ein Konzept, das ein Modul ist, das aber nicht genannt wirdmodule
. Wie auch immer, Sie haben geschrieben: "Einheiten befinden sich auf halbem Weg zwischen Namespaces und OO-Schnittstellen". Ist das nicht die Definition eines Moduls?map
und eine Einheit, die von einer Bindung abhängigmap
ist, unterscheiden sich darin, dass sich das Modul auf eine bestimmte Bindung beziehen mussmap
, z. B. die vonracket/base
, während verschiedene Benutzer der Einheit der Einheit unterschiedliche Definitionen geben könnenmap
.Anscheinend stellen Sie zwei Fragen: "Wie können Sie Modularität in funktionalen Sprachen erreichen?" Was wurde in anderen Antworten behandelt und "Wie kann man Abstraktionen in funktionalen Sprachen erstellen?" was ich antworten werde.
In OO-Sprachen tendieren Sie dazu, sich auf das Substantiv "ein Tier", "den Mailserver", "seine Gartengabel" usw. zu konzentrieren. Funktionale Sprachen betonen dagegen das Verb "laufen", "Post holen". , "anstoßen", etc.
Es ist daher keine Überraschung, dass Abstraktionen in funktionalen Sprachen eher über Verben oder Operationen als über Dingen liegen. Ein Beispiel, nach dem ich immer greife, wenn ich versuche, dies zu erklären, ist das Parsen. In funktionalen Sprachen können Sie Parser am besten schreiben, indem Sie eine Grammatik angeben und dann interpretieren. Der Interpreter erzeugt eine Abstraktion über den Parsing-Prozess.
Ein weiteres konkretes Beispiel hierfür ist ein Projekt, an dem ich vor kurzem gearbeitet habe. Ich habe eine Datenbank in Haskell geschrieben. Ich hatte eine eingebettete Sprache, um Operationen auf der untersten Ebene zu spezifizieren. So konnte ich beispielsweise Daten auf dem Speichermedium schreiben und lesen. Ich hatte eine andere, separate, eingebettete Sprache, um Operationen auf höchster Ebene zu spezifizieren. Dann hatte ich, was im Grunde genommen ein Dolmetscher ist, die Möglichkeit, Operationen von der höheren in die niedrigere Ebene umzuwandeln.
Dies ist eine bemerkenswert allgemeine Form der Abstraktion, aber nicht die einzige, die in funktionalen Sprachen verfügbar ist.
quelle
Obwohl "funktionale Programmierung" keine weitreichenden Auswirkungen auf Fragen der Modularität hat, wenden sich bestimmte Sprachen auf unterschiedliche Weise an die allgemeine Programmierung. Wiederverwendung und Abstraktion von Code interagieren dahingehend, dass es umso schwieriger ist, den Code wiederzuverwenden, je weniger Sie exponieren. Abgesehen von der Abstraktion werde ich auf zwei Aspekte der Wiederverwendbarkeit eingehen.
Statisch typisierte OOP-Sprachen verwenden traditionell nominelle Untertypen, was bedeutet, dass ein für Klasse / Modul / Schnittstelle A entwickelter Code nur dann Klasse / Modul / Schnittstelle B behandeln kann, wenn B A explizit erwähnt Dieser Code für A kann B verarbeiten, wenn B über alle Methoden und / oder Felder von A verfügt. B hätte von einem anderen Team erstellt werden können, bevor eine allgemeinere Klasse / Schnittstelle A benötigt wurde. Zum Beispiel in OCaml, strukturelle Untertypisierung Dies gilt für das Modulsystem, das OOP-ähnliche Objektsystem und seine ganz eigenen polymorphen Variantentypen.
Der auffälligste Unterschied zwischen OOP und FP wrt. Modularität ist, dass die Standard- "Einheit" in OOP als Objekt verschiedene Operationen für denselben Wertefall bündelt, während die Standard- "Einheit" in FP als Funktion dieselbe Operation für verschiedene Wertefälle bündelt. In FP ist es immer noch sehr einfach, Operationen zu bündeln, beispielsweise als Module. (Übrigens haben weder Haskell noch F # ein vollwertiges Modulsystem der ML-Familie.) Das Ausdrucksproblemist die Aufgabe, schrittweise neue Operationen hinzuzufügen, die an allen Werten arbeiten (z. B. eine neue Methode an vorhandene Objekte anhängen), und neue Fälle von Werten, die von allen Operationen unterstützt werden sollen (z. B. das Hinzufügen einer neuen Klasse mit derselben Schnittstelle). Wie in der folgenden ersten Vorlesung von Ralf Laemmel (mit ausführlichen Beispielen in C #) erläutert, ist das Hinzufügen neuer Operationen in OOP-Sprachen problematisch.
Die Kombination von OOP und FP in Scala könnte es zu einer der mächtigsten Sprachen machen. Modularität. Aber OCaml ist immer noch meine Lieblingssprache und meiner persönlichen, subjektiven Meinung nach nicht weniger als Scala. In den beiden folgenden Vorlesungen von Ralf Laemmel wird die Lösung des Ausdrucksproblems in Haskell erörtert. Ich denke, dass diese Lösung, obwohl sie perfekt funktioniert, es schwierig macht, die resultierenden Daten mit parametrischem Polymorphismus zu verwenden. Das Lösen des Expressionsproblems mit polymorphen Varianten in OCaml, das in dem unten verlinkten Jaques Garrigue-Artikel erläutert wird, weist diesen Nachteil nicht auf. Ich verweise auch auf Lehrbuchkapitel, in denen die Verwendung von Nicht-OOP- und OOP-Modularität in OCaml verglichen wird.
Nachfolgend finden Sie Haskell- und OCaml-spezifische Links, die das Ausdrucksproblem erläutern :
quelle
Tatsächlich ist OO-Code weitaus weniger wiederverwendbar, und das ist beabsichtigt. Die Idee hinter OOP ist, Operationen auf bestimmte Datenelemente auf bestimmten privilegierten Code zu beschränken, der sich entweder in der Klasse oder an der entsprechenden Stelle in der Vererbungshierarchie befindet. Dies begrenzt die nachteiligen Auswirkungen der Veränderlichkeit. Wenn sich eine Datenstruktur ändert, gibt es nur so viele Stellen im Code, die verantwortlich sein können.
Aufgrund der Unveränderlichkeit ist es Ihnen egal, wer mit einer bestimmten Datenstruktur arbeiten kann, da niemand Ihre Kopie der Daten ändern kann. Dies erleichtert das Erstellen neuer Funktionen zum Bearbeiten vorhandener Datenstrukturen erheblich. Sie erstellen einfach die Funktionen und gruppieren sie in Module, die aus Domain-Sicht angemessen erscheinen. Sie müssen sich keine Gedanken darüber machen, wo sie in die Vererbungshierarchie eingefügt werden sollen.
Die andere Art der Wiederverwendung von Code besteht darin, neue Datenstrukturen zu erstellen, um an vorhandenen Funktionen zu arbeiten. Dies wird in funktionalen Sprachen mithilfe von Funktionen wie Generika und Typklassen erledigt. Mit der Ord- Typ-Klasse von Haskell können Sie die
sort
Funktion beispielsweise für jeden Typ mit einerOrd
Instanz verwenden. Instanzen können einfach erstellt werden, wenn sie noch nicht vorhanden sind.Nehmen Sie Ihr
Animal
Beispiel und überlegen Sie, eine Fütterungsfunktion zu implementieren. Die einfache OOP-Implementierung besteht darin, eine Sammlung vonAnimal
Objekten zu verwalten und alle zu durchlaufen, wobei diefeed
Methode für jedes Objekt aufgerufen wird .Allerdings wird es schwierig, wenn es um Details geht. Ein
Animal
Objekt weiß natürlich, welche Art von Nahrung es isst und wie viel es braucht, um sich satt zu fühlen. Es ist natürlich nicht bekannt, wo das Lebensmittel aufbewahrt wird und wie viel verfügbar ist, so dass einFoodStore
Objekt zu einer Abhängigkeit von jedem geworden istAnimal
, entweder als Feld desAnimal
Objekts oder als Parameter derfeed
Methode übergeben. Um dieAnimal
Kohäsion der Klasse zu erhalten, können Sie auchfeed(animal)
zumFoodStore
Objekt wechseln oder einen Gräuel für eine Klasse erstellen, die als eineAnimalFeeder
oder eine solche bezeichnet wird.In FP gibt es keine Neigung für die Felder von und
Animal
, immer gruppiert zu bleiben, was einige interessante Implikationen für die Wiederverwendbarkeit hat. Sagen Sie bitte eine Liste habenAnimal
Datensätze, mit Feldern wiename
,species
,location
,food type
,food amount
, etc. Sie haben auch eine Liste vonFoodStore
Datensätzen mit Feldern wielocation
,food type
undfood amount
.Der erste Schritt beim Füttern könnte darin bestehen, jede dieser Listen von Aufzeichnungen auf Listen von
(food amount, food type)
Paaren abzubilden , mit negativen Zahlen für die Tiermengen. Sie können dann Funktionen erstellen, mit denen Sie mit diesen Paaren alle möglichen Aufgaben ausführen können, z. B. die Summe der Mengen der einzelnen Arten von Lebensmitteln. Diese Funktionen gehören weder zu einemAnimal
noch zu einemFoodStore
Modul, können aber von beiden in hohem Maße wiederverwendet werden.Am Ende stehen Ihnen eine Reihe von Funktionen zur Verfügung, die
[(Num A, Eq B)]
wiederverwendbar und modular sind. Sie können jedoch nur schwer herausfinden, wo Sie sie platzieren oder wie Sie sie als Gruppe bezeichnen sollen. Der Effekt ist, dass FP-Module schwieriger zu klassifizieren sind, die Klassifizierung jedoch weniger wichtig ist.quelle
Eine der gängigsten Lösungen ist das Aufteilen von Code in Module. Dies geschieht in JavaScript folgendermaßen:
Der vollständige Artikel erklärt dieses Muster in JavaScript . Abgesehen davon gibt es eine Reihe anderer Möglichkeiten, ein Modul zu definieren, z. B. RequireJS , CommonJS , Google Closure. Ein anderes Beispiel ist Erlang, wo Sie sowohl Module als auch Verhalten haben , die API und Muster erzwingen und eine ähnliche Rolle spielen wie Schnittstellen in OOP.
quelle