2017-02-21 1 views
1

Common Lisp scheint zu große Längen zu gehen, um sowohl zerstörungsfreie Funktionen (wie subst & entfernen) und destruktive Funktionen und ändern Makros (wie löschen & rotatef) für den allgemeinen Gebrauch. Vermutlich dient dies dazu, sowohl funktionale als auch nichtfunktionale Programmierstile effektiv zu unterstützen. Aber es scheint auch eine besondere Voreingenommenheit gegenüber dem nichtfunktionalen Stil beim Design des allgegenwärtigen Modells zu geben. Das setf Makro, das verallgemeinerte Referenz enthält, ist anscheinend flexibel genug, um jeden spezifizierbaren Ort zu verändern (außer vielleicht für unveränderliche ganze Zahlen und Zeichen). Diese Macht erklärt wahrscheinlich ihre weit verbreitete Verwendung trotz ihres nichtfunktionellen/destruktiven Verhaltens.Zerstörungsfreie Setf?

Die Frage ist, warum gibt es keinen entsprechenden „funktional“ nicht zerstörenden Operator, gemustert nach setf (nennen wir es gesetzt, da set bereits belegt ist), die analog zu anderen nicht zerstörenden/zerstörende Lisp Operator-Paare. Solch ein Operator würde wahrscheinlich die gleichen Argumente nehmen, einen Ort und einen Wert, würde aber eine Kopie des Objekts, in das der Ort eingebettet wurde, anstelle des neuen Werts an diesem Ort zurückgeben. Es würde wahrscheinlich auch einen universellen Kopierer von irgendeiner Art mit dem Standard setf einschließen, der die Kopie einfach modifiziert, bevor er sie zurückbringt. Der nicht zerstörende Operator könnte dann anstelle von setf für die meisten Zuweisungen verwendet werden, wobei setf für wirklich große Objekte reserviert ist. Ist ein solcher Operator angesichts der (vorausgesetzten) Anforderung an einen universellen Kopierer und der Notwendigkeit, ein Objekt von einem darin eingebetteten beliebigen Ort wiederherzustellen, möglich (oder sogar möglich)?

Antwort

2

Es gibt auch keinen universellen Setter, aber mit SETF sollten Sie einen zur Verfügung stellen, wenn Sie DEFINE-SETF-EXPANDER verwenden. Ich nehme an, Sie könnten das Äquivalent von lenses/functional references definieren. Für einen anderen Ansatz definiert Clojure eine update Funktion (entlehnt aus anderen Sprachen, ich weiß, dass sie in Prolog existiert), die auch nicht universell ist. Die Bibliothek FSET bietet einige funktionale Strukturen, insbesondere assoziative Karten, die wie die in Clojure funktionieren.

für einen Augenblick an, beschränken Sie sich auf Strukturen:

(defstruct foo a b c) 
(defstruct bar x y z) 

(defparameter *test* 
    (make-foo :a (make-bar :x 0 :y 0 :z 0) 
      :b 100 
      :c "string")) 

;; *test* is now 
;; #S(FOO :A #S(BAR :X 0 :Y 0 :Z 0) :B 100 :C "string") 

Der folgende Ansatz eine Kopie macht, es beruht auf copy-structure und slot-value, die zwar nicht Standard ist well supported:

(defun update (structure keys value) 
    (if (endp keys) 
     value 
     (destructuring-bind (key &rest keys) keys 
     (let ((copy (copy-structure structure))) 
      (setf (slot-value copy key) 
       (update (slot-value copy key) 
         keys 
         value)) 
      copy)))) 

Sie können *test* wie folgt aktualisieren:

(update *test* '(a z) 10) 

Hier ist eine Spur:

0: (UPDATE #S(FOO :A #S(BAR :X 0 :Y 0 :Z 0) :B 100 :C "string") (A Z) 10) 
    1: (UPDATE #S(BAR :X 0 :Y 0 :Z 0) (Z) 10) 
    2: (UPDATE 0 NIL 10) 
    2: UPDATE returned 10 
    1: UPDATE returned #S(BAR :X 0 :Y 0 :Z 10) 
0: UPDATE returned #S(FOO :A #S(BAR :X 0 :Y 0 :Z 10) :B 100 :C "string") 

Um zu verallgemeinern Sie eine Funktion anstelle eines Wertes annehmen könnte, so dass man das Äquivalent von incf durch teilweise Anwendung die Update-Funktion mit #'1+ (die resultierende Schließung implementieren könnte würde eine Liste von Schlüssel akzeptieren).

Auch müssen Sie die Kopie Operation verallgemeinern, die mit generischen Funktionen möglich ist. Ebenso können Sie andere Arten von Zugriffsmethoden verwenden und slot-value durch eine generische access-Funktion ersetzen (für die es eine (setf access)-Operation gibt). Dieser generische Kopier/Setf-Ansatz könnte jedoch verschwenderisch sein, wenn Sie einige Daten teilen möchten. Beispielsweise müssen Sie nur den Teil einer Liste kopieren, der zu Ihren Daten führt, nicht die nachfolgenden Zellen.

Sie könnten einige Anlage zur Definition benutzerdefinierte updaters definieren:

(defmethod perform-update (new (list list) position) 
    (nconc (subseq list 0 position) 
     (list new) 
     (nthcdr (1+ position) list))) 
+0

Nehmen wir an, Sie beschränken das Kopieren auf grundlegende Lisp-Objekte (abgesehen von CLOS-Instanzen), ist das Wesen der universellen Kopieridee, dann rekursive generische Methoden für jeden Typ von Basisobjekt zu schreiben? – davypough

+0

@davypough Ja, die standardmäßige universelle Kopie könnte eine Kopie aller Komponenten einer Datenstruktur erstellen (nur eine Kopierstufe, die oben gezeigte Aktualisierungsfunktion führt die rekursive Kopie bei Bedarf durch). Beachten Sie, dass dies ein Problem mit Zirkelreferenzen sein wird. – coredump

6

Common Lisp hat keinen Universal-Kopierer aus dem gleichen Grunde sie nicht über eine eingebaute in druckbare Darstellung von CLOS objects (siehe z.B. Saving CLOS objects): die Macht der MOP.

Speziell die Objekterzeugung kann willkürliche Nebeneffekte haben, deren Replikation schwer zu gewährleisten ist. Zum Beispiel definieren Sie initialize-instance für Ihre Klasse, um Slots zu modifizieren, die etwas Flüssiges bilden (z.B. Gezeiten oder nur random). Dann kann das Ergebnis make-instance mit den gleichen Argumenten zweimal genannt werden. Sie könnten argumentieren, dass dies ein Argument für einen generischen Kopierer memcpy ist. Stellen Sie sich jedoch jetzt eine Klasse vor, die Instanzkonten verwaltet (ein :allocation :class Slot, der eine Hash-Tabelle enthält, die eine eindeutige ID zu Instanz zuordnet). Jetzt wird die Hin-und Rückfahrt von dem kopierten Objekt über die Tabelle ein anderes Objekt (das Original, nicht die Kopie) - eine große Vertragsverletzung. Und diese Beispiele sind nur die Spitze des Eisbergs.

Allerdings würde ich nicht zustimmen, dass Common Lisp imperative Stil mehr als funktionalen Stil fördert. Es entmutigt nur den Stil, den Sie beschreiben (Kopie + setf). Denken Sie so darüber nach: Aus dem funktionalen POV ist eine Kopie nicht vom Original zu unterscheiden (weil alles unveränderlich ist).

Es gibt auch "kleine Hinweise", die eine klare Präferenz für den funktionalen Stil zeigen. Beachten Sie, dass die "natürlichen" Namen (z. B. append) reserviert sind für nicht-destruktive Funktionen; Die destruktiven Versionen (z. B. nconc) erhalten obskure Namen.

Zusätzlich pathnames, characters und numbers (einschließlich Komposite wie ratio und complex) sind unveränderliche, d.h.Alle Funktionen für Pfad und Nummer erzeugen neue Objekte (Funktionsstil).

So, IMO, Common Lisp ermutigt Programmierer, funktionale Stil zu verwenden, während immer noch imperativ Stil machbar, in Übereinstimmung mit dem Slogan "einfache Dinge sollten einfach sein, sollten harte Dinge möglich sein".

Siehe auch Kent Pitman's writeup.

+0

Es sieht aus wie Sie die oben genannten CLOS Objekt-Leser/Schreiber schrieb. (Danke!) Gibt es ein gutes Beispiel für eine Beschränkung für bestimmte Arten von Objekten zur Verfügung? – davypough

+0

Fügen Sie eine 'after' Methode zu' make-instance' hinzu, die das Objekt basierend auf der Mondphase modifiziert. Dann ist das Objekt, das Sie speichern, anders als das Objekt, das Sie lesen. – sds

Verwandte Themen