it-swarm.com.de

Funktionale Programmierung im Vergleich zu OOP mit Klassen

Ich habe mich in letzter Zeit für einige Konzepte der funktionalen Programmierung interessiert. Ich verwende seit einiger Zeit OOP. Ich kann sehen, wie ich eine ziemlich komplexe App in OOP erstellen würde. Jedes Objekt würde wissen, wie man Dinge macht, die dieses Objekt macht. Oder alles, was seine Elternklasse macht Also kann ich einfach Person().speak() anweisen, die Person zum Sprechen zu bringen.

Aber wie mache ich ähnliche Dinge in der funktionalen Programmierung? Ich sehe, wie Funktionen erstklassige Gegenstände sind. Aber diese Funktion macht nur eine bestimmte Sache. Hätte ich einfach eine say() -Methode im Umlauf und würde sie mit einem Äquivalent des Arguments Person() aufrufen, damit ich weiß, was so etwas sagt?

So kann ich die einfachen Dinge sehen, wie würde ich das Vergleichbare von OOP und Objekten in der funktionalen Programmierung machen, damit ich meine Codebasis modularisieren und organisieren kann?

Als Referenz ist meine primäre Erfahrung mit OOP Python, PHP und einigen C #. Die Sprachen, die ich betrachte und die funktionale Merkmale aufweisen, sind Scala und Haskell) Obwohl ich mich zu Scala neige.

Grundlegendes Beispiel (Python):

Animal(object):
    def say(self, what):
        print(what)

Dog(Animal):
    def say(self, what):
        super().say('dog barks: {0}'.format(what))

Cat(Animal):
    def say(self, what):
        super().say('cat meows: {0}'.format(what))

dog = Dog()
cat = Cat()
dog.say('ruff')
cat.say('purr')
34
skift

Was Sie hier wirklich fragen, ist, wie man Polymorphismus in funktionalen Sprachen macht, d. H. Wie man Funktionen erstellt, die sich aufgrund ihrer Argumente unterschiedlich verhalten.

Beachten Sie, dass das erste Argument für eine Funktion normalerweise dem "Objekt" in OOP entspricht, aber in funktionalen Sprachen möchten Sie normalerweise Funktionen von Daten trennen Das "Objekt" ist wahrscheinlich ein reiner (unveränderlicher) Datenwert.

Funktionale Sprachen bieten im Allgemeinen verschiedene Möglichkeiten, um Polymorphismus zu erreichen:

  • So etwas wie Multimethoden, die eine andere Funktion aufrufen, basierend auf der Prüfung der bereitgestellten Argumente. Dies kann für den Typ des ersten Arguments erfolgen (was effektiv dem Verhalten der meisten OOP Sprachen) entspricht, kann aber auch für andere Attribute der Argumente erfolgen.
  • Prototyp-/objektähnliche Datenstrukturen, die erstklassige Funktionen als Mitglieder enthalten . So können Sie eine "Sagen" -Funktion in Ihre Datenstrukturen für Hunde und Katzen einbetten. Tatsächlich haben Sie den Code zu einem Teil der Daten gemacht.
  • Pattern Matching - wobei die Pattern Matching-Logik in die Funktionsdefinition integriert ist und unterschiedliche Verhaltensweisen für verschiedene Parameter sicherstellt. In Haskell üblich.
  • Verzweigung/Bedingungen - entspricht if/else-Klauseln in OOP. Ist möglicherweise nicht sehr erweiterbar, kann aber in vielen Fällen dennoch geeignet sein, wenn Sie einen begrenzten Satz möglicher Werte haben (z. B. wurde der Funktion eine Zahl, eine Zeichenfolge oder eine Null übergeben?).

Als Beispiel sehen Sie hier eine Clojure-Implementierung Ihres Problems mit mehreren Methoden:

;; define a multimethod, that dispatched on the ":type" keyword
(defmulti say :type)  

;; define specific methods for each possible value of :type. You can add more later
(defmethod say :cat [animal what] (println (str "Car purrs: " what)))
(defmethod say :dog [animal what] (println (str "Dog barks: " what)))
(defmethod say :default [animal what] (println (str "Unknown noise: " what)))

(say {:type :dog} "ruff")
=> Dog barks: ruff

(say {:type :ape} "ook")
=> Unknown noise: ook

Beachten Sie, dass für dieses Verhalten keine expliziten Klassen definiert werden müssen: Normale Karten funktionieren einwandfrei. Die Versandfunktion (in diesem Fall Typ) kann eine beliebige Funktion der Argumente sein.

22
mikera

Dies ist keine direkte Antwort und auch nicht unbedingt 100% genau, da ich kein Experte für funktionale Sprachen bin. Aber in jedem Fall werde ich meine Erfahrungen mit Ihnen teilen ...

Vor ungefähr einem Jahr war ich in einem ähnlichen Boot wie Sie. Ich habe C++ und C # gemacht und alle meine Designs waren immer sehr schwer mit OOP. Ich habe von FP Sprachen gehört, einige Informationen online gelesen, das F # -Buch durchgeblättert, konnte aber immer noch nicht wirklich verstehen, wie eine FP Sprache OOP oder ersetzen kann Seien Sie im Allgemeinen nützlich, da die meisten Beispiele, die ich gesehen habe, einfach zu einfach waren.

Für mich kam der "Durchbruch", als ich mich entschied, Python zu lernen. Ich habe Python heruntergeladen, bin dann zu Project Euler Homepage gegangen und habe gerade angefangen, ein Problem nach dem anderen zu lösen. Python ist nicht unbedingt eine FP Sprache, und Sie können sicherlich Klassen darin erstellen, aber im Vergleich zu C++/Java/C # enthält es viel mehr von FP Konstrukte, also als ich anfing damit zu spielen, traf ich eine bewusste Entscheidung, keine Klasse zu definieren, es sei denn, ich musste es unbedingt.

Was ich an Python interessant fand, war, wie einfach und natürlich es war, Funktionen zu übernehmen und sie zu "nähen", um komplexere Funktionen zu erstellen, und am Ende wurde Ihr Problem immer noch durch Aufrufen einer einzelnen Funktion gelöst.

Sie haben darauf hingewiesen, dass Sie beim Codieren dem Prinzip der Einzelverantwortung folgen sollten, und das ist absolut richtig. Nur weil die Funktion für eine einzelne Aufgabe verantwortlich ist, heißt das nicht, dass sie nur das absolute Minimum leisten kann. In FP haben Sie noch Abstraktionsebenen. Ihre Funktionen auf höherer Ebene können also immer noch "eine" Sache ausführen, aber sie können an Funktionen auf niedrigerer Ebene delegieren, um genauere Details darüber zu implementieren, wie diese "eine" Sache erreicht wird.

Der Schlüssel zu FP ist jedoch, dass Sie keine Nebenwirkungen haben. Solange Sie die Anwendung als einfache Datentransformation mit definierten Eingaben und Ausgaben behandeln, können Sie FP Code schreiben, der das erreicht, was Sie benötigen. Natürlich passt nicht jede Anwendung gut in diese Form, aber sobald Sie damit beginnen, werden Sie überrascht sein, wie viele Anwendungen passen. Und hier denke ich, dass Python, F # oder Scala glänzen, weil sie Ihnen FP Konstrukte geben, aber wenn Sie sich an Ihren Zustand erinnern und "Nebenwirkungen einführen" müssen, können Sie immer fallen zurück zu wahren und erprobten OOP Techniken.

Seitdem habe ich eine ganze Reihe von python Code als Dienstprogramme und andere Hilfsskripte für die interne Arbeit geschrieben, und einige davon sind ziemlich weit verkleinert, aber ich erinnere mich an die grundlegenden SOLID Prinzipien von diesem Code kam immer noch sehr wartbar und flexibel heraus. Genau wie in OOP ist Ihre Schnittstelle eine Klasse und Sie verschieben Klassen, während Sie Funktionen umgestalten und/oder hinzufügen, in FP tun Sie genau dasselbe mit Funktionen.

Letzte Woche habe ich angefangen, in Java zu codieren, und seitdem werde ich fast täglich daran erinnert, dass ich in OOP Schnittstellen implementieren muss, indem ich Klassen mit Methoden deklariere, die Funktionen überschreiben. In einigen Fällen könnte ich das erreichen Dasselbe in Python mit einem einfachen Lambda-Ausdruck, zum Beispiel 20-30 Codezeilen, die ich zum Scannen eines Verzeichnisses geschrieben habe, wären 1-2 Zeilen in Python und gewesen keine Klassen.

FP selbst sind höhere Sprachen. In Python (sorry, meine einzige FP Erfahrung) könnte ich das Listenverständnis in einem anderen Listenverständnis mit Lambdas und anderen Dingen zusammenfassen, und das Ganze würde nur 3-4 Zeilen umfassen von Code. In C++ könnte ich absolut das Gleiche erreichen, aber da C++ eine niedrigere Ebene ist, müsste ich viel mehr Code als 3-4 Zeilen schreiben, und wenn die Anzahl der Zeilen zunimmt, würde mein SRP-Training beginnen und ich würde beginnen Überlegen Sie, wie Sie Code in kleinere Teile aufteilen können (dh mehr Funktionen). Aber im Interesse der Wartbarkeit und des Versteckens von Implementierungsdetails würde ich all diese Funktionen in dieselbe Klasse einordnen und sie privat machen. Und da haben Sie es ... Ich habe gerade eine Klasse erstellt, während ich in python "return (.... lambda x: .. ....)" geschrieben hätte.

13
DXM

In Haskell ist "Klasse" am nächsten. Diese Klasse jedoch nicht die gleiche wie die Klasse in Java und C++, funktioniert in diesem Fall für das, was Sie wollen.

In Ihrem Fall sieht Ihr Code so aus.

 Klasse Tier a wo 
 sagen :: String -> Ton 

Dann können Sie einzelne Datentypen haben, die diese Methoden anpassen.

 Beispiel Animal Dog, wo 
 s = "bark" ++ s 
 sagen

BEARBEITEN: - Bevor Sie sich auf Hund spezialisieren können, müssen Sie dem System mitteilen, dass Hund ein Tier ist.

 Daten Hund =\- etwas hier -\(Tier ableiten) 

EDIT: - Für Wilq.
Wenn Sie nun say in einer Funktion say foo verwenden möchten, müssen Sie haskell mitteilen, dass foo nur mit Animal funktionieren kann.

 Foo :: (Tier a) => a -> String -> String 
 Foo a str = sag a str 

wenn du jetzt mit Hund foo rufst, bellt es, wenn du mit Katze rufst, miaut es.

 main = do 
 Lasse d = dog (\ - cstr parameters - \) 
 C = cat 
 In show $ foo d "Hello World" 

Sie können jetzt keine andere Definition der Funktion sagen lassen. Wenn say mit etwas aufgerufen wird, das nicht tierisch ist, führt dies zu einem Kompilierungsfehler.

8
Manoj R

Funktionale Sprachen verwenden zwei Konstrukte, um Polymorphismus zu erreichen:

  • Funktionen erster Ordnung
  • Generika

Das Erstellen von polymorphem Code mit diesen unterscheidet sich grundlegend von der Verwendung von Vererbung und virtuellen Methoden durch OOP. Während beide möglicherweise in Ihrer bevorzugten OOP Sprache (wie C #) verfügbar sind, können die meisten funktionalen Sprachen (wie Haskell) bis zu elf verwenden. Es ist selten, dass Funktionen nicht generisch sind, und die meisten Funktionen haben Funktionen als Parameter.

Es ist schwer so zu erklären und es wird viel Zeit in Anspruch nehmen, diesen neuen Weg zu lernen. Aber um dies zu tun, müssen Sie OOP vollständig vergessen, da dies in der funktionalen Welt nicht so funktioniert.

6
Euphoric

es hängt wirklich davon ab, was Sie erreichen wollen.

wenn Sie nur eine Möglichkeit benötigen, das Verhalten anhand selektiver Kriterien zu organisieren, können Sie z. ein Wörterbuch (Hash-Tabelle) mit Funktionsobjekten. in python könnte es etwas in der Art von sein:

def bark(what):
    print "barks: {0}".format(what) 

def meow(what):
    print "meows: {0}".format(what)

def climb(how):
    print "climbs: {0}".format(how)

if __name__ == "__main__":
    animals = {'dog': {'say': bark},
               'cat': {'say': meow,
                       'climb': climb}}
    animals['dog']['say']("ruff")
    animals['cat']['say']("purr")
    animals['cat']['climb']("well")

beachten Sie jedoch, dass (a) es keine 'Instanzen' von Hund oder Katze gibt und (b) Sie den 'Typ' Ihrer Objekte selbst verfolgen müssen .

wie zum Beispiel: pets = [['martin','dog','grrrh'], ['martha', 'cat', 'zzzz']]. dann könnten Sie ein Listenverständnis wie [animals[pet[1]]['say'](pet[2]) for pet in pets] machen

0
kr1

OO-Sprachen können anstelle von einfachen Sprachen verwendet werden, um manchmal direkt mit einer Maschine zu kommunizieren. C++ Sicher, aber auch für C # gibt es Adapter und so. Obwohl das Schreiben von Code zur Steuerung mechanischer Teile und die minutiöse Kontrolle über den Speicher am besten so niedrig wie möglich gehalten werden. Wenn sich diese Frage jedoch auf aktuelle objektorientierte Software wie Branchen, Webanwendungen, IOT, Webdienste und die meisten massenverwendeten Anwendungen bezieht, dann ...

Antwort, falls zutreffend

Leser könnten versuchen, mit einer serviceorientierten Architektur (SOA) zu arbeiten. Das heißt, DDD, N-Layered, N-Tiered, Hexagonal, was auch immer. Ich habe nicht gesehen, dass eine Großunternehmensanwendung "Traditional" OO (Active-Record oder Rich-Models)) effizient verwendet, wie es in den 70er und 80er Jahren im letzten Jahrzehnt sehr häufig beschrieben wurde. ( Siehe Anmerkung 1)

Der Fehler liegt nicht beim OP, aber es gibt ein paar Probleme mit der Frage.

  1. Das Beispiel, das Sie bereitstellen, dient lediglich der Demonstration des Polymorphismus. Es handelt sich nicht um Produktionscode. Manchmal werden genau solche Beispiele wörtlich genommen.

  2. In FP und SOA werden Daten von der Geschäftslogik getrennt. Das heißt, Daten und Logik gehören nicht zusammen. Logik geht in Dienste und Daten (Domänenmodelle) haben kein polymorphes Verhalten ( Siehe Anmerkung 2).

  3. Dienste und Funktionen können polymorph sein. In FP übergeben Sie häufig Funktionen als Parameter an andere Funktionen anstelle von Werten. Sie können dasselbe in OO Sprachen mit Typen wie Callable oder Func tun, aber es ist nicht weit verbreitet (siehe Hinweis 3). In FP und SOA) sind Ihre Modelle nicht polymorph, sondern nur Ihre Dienste/Funktionen (siehe Hinweis 4).

  4. In diesem Beispiel gibt es einen schlimmen Fall von Hardcodierung. Ich spreche nicht nur von der roten Schnur "Hundebellen". Ich spreche auch über das CatModel und das DogModel selbst. Was passiert, wenn Sie ein Schaf hinzufügen möchten? Sie müssen in Ihren Code gehen und neuen Code erstellen? Warum? Im Produktionscode würde ich lieber nur ein AnimalModel mit seinen Eigenschaften sehen. Im schlimmsten Fall ein AmphibianModel und ein FowlModel, wenn ihre Eigenschaften und Handhabung so unterschiedlich sind.

Dies ist, was ich in einer aktuellen "OO" -Sprache erwarten würde:

public class Animal
{
    public int AnimalID { get; set; }
    public int LegCount { get; set; }
    public string Name { get; set; }
    public string WhatISay { get; set; }
}

public class AnimalService : IManageAnimals
{
    private IPersistAnimals _animalRepo;
    public AnimalService(IPersistAnimals animalRepo) { _animalRepo = animalRepo; }

    public List<Animal> GetAnimals() => _animalRepo.GetAnimals();

    public string WhatDoISay(Animal animal)
    {
        if (!string.IsNullOrWhiteSpace(animal.WhatISay))
            return animal.WhatISay;

        return _animalRepo.GetAnimalNoise(animal.AnimalID);
    }
}

(Basic Flow

Wie bewegen Sie sich von Klassen in OO zu funktionaler Programmierung? Wie andere gesagt haben; Sie können, aber Sie tun es nicht wirklich. Der Sinn des oben Gesagten besteht darin, zu demonstrieren, dass Sie nicht einmal sollten Verwenden Sie Klassen (im traditionellen Sinne der Welt), wenn Sie Java und C #) ausführen. Sobald Sie mit dem Schreiben von Code in einer serviceorientierten Architektur (DDD, Layered, Tiered, Hexagonal, was auch immer) beginnen. Sie sind Functional einen Schritt näher, da Sie Ihre Daten (Domänenmodelle) von Ihren logischen Funktionen (Diensten) trennen.

OO Sprache einen Schritt näher an FP

Sie können sogar noch etwas weiter gehen und Ihre SOA Services) in zwei Typen aufteilen.

Optionaler Klassentyp 1 : Allgemeine Schnittstellenimplementierungsdienste für Einstiegspunkte. Dies wären "unreine" Einstiegspunkte, die andere Funktionen "rein" oder "unrein" aufrufen können. Dies können Ihre Einstiegspunkte aus einer RESTful-API sein.

Optionaler Klassentyp 2 : Pure Business Logic Services. Dies sind statische Klassen mit der Funktion "Pure". In FP bedeutet "Pure", dass keine Nebenwirkungen auftreten. Status oder Persistenz werden nirgendwo explizit festgelegt. (Siehe Anmerkung 5)

Wenn Sie also an Klassen in objektorientierten Sprachen denken, die in einer serviceorientierten Architektur verwendet werden, kommt dies nicht nur Ihrem OO Code) zugute, sondern lässt die funktionale Programmierung sehr einfach verständlich erscheinen.

Anmerkungen

Hinweis 1 : Das objektorientierte Design "Original" Rich "oder" Active-Record "ist noch vorhanden. Es gibt eine Menge solchen Legacy-Codes, als die Leute vor einem Jahrzehnt oder länger "es richtig gemacht" haben. Das letzte Mal, als ich sah, dass diese Art von Code (richtig gemacht) aus einem Videospiel Codebase in C++ stammte, wo sie den Speicher genau kontrollierten und nur sehr begrenzten Speicherplatz hatten. Ganz zu schweigen von FP und serviceorientierte Architekturen sind Bestien und sollten Hardware nicht berücksichtigen. Sie legen jedoch die Fähigkeit, sich ständig zu ändern, gewartet zu werden, variable Datengrößen zu haben und andere Aspekte als Priorität fest. In Videospielen und Maschinen-KI steuern Sie die Signale und Daten sehr genau.

Hinweis 2 : Domänenmodelle haben weder polymorphes Verhalten noch externe Abhängigkeiten. Sie sind "isoliert". Das bedeutet nicht, dass sie 100% anämisch sein müssen. Sie können eine Menge Logik in Bezug auf ihre Konstruktion und veränderbare Eigenschaftsänderung haben, wenn dies zutrifft. Siehe DDD "Value Objects" und Entities von Eric Evans und Mark Seemann.

Anmerkung 3 : Linq und Lambda sind sehr häufig. Wenn ein Benutzer eine neue Funktion erstellt, verwendet er selten Func oder Callable als Parameter, während es in FP] seltsam wäre, eine App ohne Funktionen zu sehen, die diesem Muster folgen.

Anmerkung 4 : Polymorphismus nicht mit Vererbung verwechseln. Ein CatModel erbt möglicherweise AnimalBase, um festzustellen, welche Eigenschaften ein Tier normalerweise hat. Aber wie ich zeige, sind Modelle wie dieses ein Code-Geruch . Wenn Sie dieses Muster sehen, können Sie es möglicherweise zerlegen und in Daten umwandeln.

Hinweis 5 : Reine Funktionen können (und tun) Funktionen als Parameter akzeptieren. Die eingehende Funktion ist möglicherweise unrein, aber möglicherweise rein. Zu Testzwecken wäre es immer rein. Obwohl es in der Produktion als rein behandelt wird, kann es Nebenwirkungen haben. Das ändert nichts an der Tatsache, dass die reine Funktion rein ist. Die Parameterfunktion ist jedoch möglicherweise unrein. Nicht verwirrend! : D.

0
Suamere