2015-12-20 8 views
11

Hintergrundgeschichte: Ich habe viele große und relativ komplexe Projekte in Java gemacht, habe viel Erfahrung in Embedded C Programmierung. Ich habe mich mit Schema und CL-Syntax vertraut gemacht und einige einfache Programme mit Racket geschrieben.Lisp-Familie: Wie kann man objektorientiertem Java-ähnlichem Denken entkommen?

Frage: Ich habe ein ziemlich großes Projekt geplant und möchte es im Racket machen. Ich habe eine Menge gehört "Wenn du" Lispelst, wirst du ein besserer Programmierer "usw. Aber jedes Mal, wenn ich versuche, ein Programm zu planen oder zu schreiben, zerlege ich die Aufgabe immer noch mit vertrauten statusbehafteten Objekten mit Schnittstellen.
Gibt es "Entwurfsmuster" für Lisp? Wie bekommt man Lisp-Familie "mojo"? Wie kann man der objektorientierten Beschränkung des Denkens entkommen? Wie kann man funktionale Programmierungsideen anwenden, die durch mächtige Makrofaktoren unterstützt werden? Ich habe versucht, den Quellcode großer Projekte auf GitHub zu studieren (Light Table, zum Beispiel) und bin eher verwirrt als aufgeklärt.
EDIT1 (weniger ambivalente Fragen): Gibt es eine gute Literatur zu diesem Thema, die Sie empfehlen können oder gibt es gute Open-Source-Projekte in hoher Qualität, die als gutes Beispiel dienen können?

+0

Hier ist eine interessante Lektüre, warum Designmuster in Lisp-ähnlichen Sprachen weniger verbreitet sind als in anderen Sprachen: http://norvig.com/design-patterns/ – uselpa

+1

Eine Methode besteht darin, einfach von oben nach unten zu schreiben und vorausgesetzt, Sie haben Funktionen, die das tun, was Sie wollen. Sobald Sie das Problem als eine Art Berechnung definiert haben, fallen mehr funktionale Idiome heraus. – WorBlux

Antwort

6

Eine Reihe von "Paradigmen" sind im Laufe der Jahre in Mode gekommen: strukturierte Programmierung, objektorientiert, funktional, etc. Mehr wird kommen.

Auch wenn ein Paradigma nicht mehr in Mode ist, kann es immer noch gut darin sein, die speziellen Probleme zu lösen, die es zuerst populär gemacht haben.

So zum Beispiel mit OOP für eine GUI ist immer noch natürlich. (Die meisten GUI-Frameworks haben eine Reihe von Zuständen, die durch Nachrichten/Ereignisse modifiziert wurden.)

Racket ist Multi-Paradigma. Es hat ein class System. Ich verwende es selten, , aber es ist verfügbar, wenn ein OO-Ansatz für das Problem sinnvoll ist. Common Lisp hat Multimethoden und CLOS. Clojure hat Multimethoden und Java Klassen Interop.

Und trotzdem, Grund Stateful OOP ~ = eine Variable in einem Verschluss mutiert:

#lang racket 

;; My First Little Object 
(define obj 
    (let ([val #f]) 
    (match-lambda* 
     [(list)   val] 
     [(list 'double) (set! val (* 2 val))] 
     [(list v)  (set! val v)]))) 

obj   ;#<procedure:obj> 
(obj)   ;#f 
(obj 42) 
(obj)   ;42 
(obj 'double) 
(obj)   ;84 

Ist das ein großes Objekt-System? Nein. Aber es hilft Ihnen zu sehen, dass die Essenz von OOP den Zustand mit Funktionen kapselt, die ihn modifizieren. Und Sie können dies in Lisp leicht tun.


Was ich immer an: Ich glaube nicht, Lisp verwendet, ist über „anti-OOP“ oder „pro-funktional“ zu sein. Stattdessen ist es eine großartige Möglichkeit, mit den Grundbausteinen der Programmierung zu spielen (und in der Produktion zu verwenden). Sie können verschiedene Paradigmen erkunden. Sie können mit Ideen wie "Code ist Daten und umgekehrt" experimentieren.

Ich sehe Lisp nicht als eine Art spirituelle Erfahrung. Höchstens ist es wie Zen, und Satori ist die Erkenntnis, dass all diese Paradigmen nur verschiedene Seiten derselben Medaille sind. Sie sind alle wundervoll, und sie alle saugen. Das Paradigma, das auf die Lösung hinweist, ist nicht die Lösung. Blah bla bla.:)

Mein praktischer Rat ist, es klingt wie Sie Ihre Erfahrung mit funktionaler Programmierung abrunden möchten. Wenn Sie das zum ersten Mal an einem großen Projekt machen müssen, ist das eine Herausforderung. Versuchen Sie in diesem Fall, Ihr Programm in Stücke zu zerlegen, die "Status beibehalten" vs. "Dinge berechnen". In letzterem können Sie versuchen, sich auf "mehr Funktionalität" zu konzentrieren. Suchen Sie nach Möglichkeiten, um reine Funktionen zu schreiben. Kette sie zusammen. Erfahren Sie, wie Sie Funktionen höherer Ordnung verwenden. Und schließlich, verbinden Sie sie mit dem Rest Ihrer Anwendung - die weiterhin Stateful und OOP und Imperativ sein kann. Das ist in Ordnung, für jetzt und vielleicht für immer.

2

Die Entwurfsmuster "Gang of 4" gelten für die Lisp-Familie genauso wie für andere Sprachen. Ich benutze CL, also ist dies eher eine CL Perspektive/Kommentar.

Hier ist der Unterschied: Denken Sie in Bezug auf Methoden, die auf Familien von Typen arbeiten. Genau darum geht es bei defgeneric und defmethod. Sie sollten defstruct und defclass als Container für Ihre Daten verwenden, wobei zu beachten ist, dass Sie nur Zugriff auf die Daten erhalten. defmethod ist im Grunde Ihre übliche Klassenmethode (mehr oder weniger) aus Sicht eines Betreibers auf eine Gruppe von Klassen oder Typen (Mehrfachvererbung.)

Sie werden feststellen, dass Sie defun und define viel verwenden werden. Das ist normal. Wenn Sie in Parameterlisten und zugeordneten Typen Gemeinsamkeiten sehen, optimieren Sie mit defgeneric/defmethod. (Suchen Sie beispielsweise nach einem CL-Quadtree-Code auf github.)

Makros: Nützlich, wenn Sie Code um eine Reihe von Formularen kleben müssen. Zum Beispiel, wenn Sie sicherstellen müssen, dass Ressourcen wiederhergestellt werden (Schließen von Dateien) oder der C++ - "Protokoll" -Stil mithilfe geschützter virtueller Methoden, um eine spezifische Vor- und Nachbearbeitung sicherzustellen.

Und, schließlich, zögern Sie nicht, ein lambda zurückzugeben, um interne Maschinerie einzukapseln. Das ist wahrscheinlich der beste Weg, um einen Iterator zu implementieren ("let over Lambda" -Stil.)

Hoffe, dass Sie damit beginnen.

+1

Wer ist das fünfte Bandenmitglied? – uselpa

+6

Ringo Starr, natürlich. –

2

Eine persönliche Ansicht:

Wenn Sie ein Objekt-Design in den Namen der Klassen und ihre Methoden parametrisieren - wie Sie mit C++ Vorlagen tun könnten - dann am Ende mit etwas, das ganz wie ein funktionalen Design aussieht. Mit anderen Worten, funktionale Programmierung macht keine nutzlosen Unterscheidungen zwischen ähnlichen Strukturen, weil ihre Teile unterschiedliche Namen haben.

Mein Engagement auf Clojure, gewesen, die das gute Stück von Objektprogrammierung

  • arbeiten Schnittstellen

zu stehlen versucht, während die zwielichtigen und nutzlos Bits Verwerfen

  • Beton Vererbung
  • traditionelle Daten verstecken.

Meinungen variieren, wie erfolgreich dieses Programm gewesen ist.

Da Clojure in Java (oder einem Äquivalent) ausgedrückt wird, können Objekte nicht nur tun, was Funktionen tun können, es gibt auch eine regelmäßige Zuordnung von einem zum anderen.

Wo also kann irgendein funktioneller Vorteil liegen? Ich würde sagen, Ausdruckskraft. Es gibt viele sich wiederholende Dinge, die Sie in Programmen tun, die es nicht wert sind, in Java erfasst zu werden - wer hat Lambdas verwendet, bevor Java die kompakte Syntax für sie bereitgestellt hat? Aber der Mechanismus war immer da.

Und Lisps haben Makros, die bewirken, dass alle Strukturen erstklassig sind. Und es gibt eine Synergie zwischen diesen Aspekten, die Sie genießen werden.

4

Eine Möglichkeit zum Vergleich der Programmierung in OO vs Lisp (und "funktionale" Programmierung im Allgemeinen) ist zu sehen, was jedes "Paradigma" für den Programmierer ermöglicht. Ein Gesichtspunkt in dieser Argumentationskette, die sich auf Repräsentationen von Daten bezieht, ist, dass der OO-Stil das Erweitern von Datendarstellungen erleichtert, aber das Hinzufügen von Datenoperationen erschwert. Im Gegensatz dazu vereinfacht der funktionale Stil das Hinzufügen von Operationen, erschwert jedoch das Hinzufügen neuer Datendarstellungen.

Konkret, wenn es eine Druckerschnittstelle mit OO gibt, ist es sehr einfach, eine neue HPPrinter-Klasse hinzuzufügen, die die Schnittstelle implementiert. Wenn Sie jedoch einer vorhandenen Schnittstelle eine neue Methode hinzufügen möchten, müssen Sie jede vorhandene Klasse bearbeiten Das implementiert die Schnittstelle, was schwieriger ist und möglicherweise unmöglich ist, wenn die Klassendefinitionen in einer Bibliothek verborgen sind.

Im Gegensatz dazu sind Funktionen (anstelle von Klassen) mit dem funktionalen Stil die Einheit des Codes, so dass man leicht eine neue Operation hinzufügen kann (schreiben Sie einfach eine Funktion). Jede Funktion ist jedoch für die Verteilung gemäß der Art der Eingabe verantwortlich. Um eine neue Datendarstellung hinzuzufügen, müssen alle vorhandenen Funktionen bearbeitet werden, die mit dieser Art von Daten arbeiten.

Die Festlegung, welcher Stil für Ihre Domäne am besten geeignet ist, hängt davon ab, ob Sie eher Darstellungen oder Vorgänge hinzufügen.

Dies ist natürlich eine Verallgemeinerung auf hohem Niveau, und jeder Stil hat Lösungen entwickelt, um die erwähnten Kompromisse (zB Mixins für OO) zu bewältigen, aber ich denke, dass es immer noch in hohem Maße gilt.

Here is a well-known academic paper, dass die Idee vor 25 Jahren erfasst.

Here are some notes from a recent course (ich lehrte) beschreibt die gleiche Philosophie.

(Beachten Sie, dass der Kurs der How to Design Programs Lehrplan folgt, die zunächst den funktionalen Ansatz betont, aber später geht in den OO-Stil.)

bearbeiten: Dies ist natürlich nur Antworten auf Ihre Frage Teil und bezieht sich nicht auf die (mehr oder weniger orthogonal) Thema von Makros. Dazu beziehe ich mich auf Greg Hendershott's excellent tutorial.

+3

Dies trifft nicht wirklich auf etwas wie das Common Lisp Object System zu, wo es aufgrund der Mischung aus offenen Klassen, Mehrfachvererbung, Trivial Mixins, keine Interfaces, Methodenkombinationen, Multi-Methoden, einfach ist, beide Klassen oder Methoden hinzuzufügen. usw. –

+1

@RainerJoswig Natürlich hast du Recht.Viele "Lösungen" existieren für das "Ausdrucksproblem", aber ich denke, es ist immer noch eine nützliche Darstellung der beiden Stile für diejenigen, die nicht vertraut sind. – stchang

+0

Das 'Ausdruck Problem' ist ein Problem mit statisch typisierten Sprachen mit geschlossenen Klassen. Die meisten Lisp-Objektsysteme fallen nicht in diese Kategorie. –

Verwandte Themen