7 Stimmen

Verwenden einer Lambda-Wert aus Funktion als erstes Element der Liste

Ich lese gerade Peter Norvigs Paradigmen der künstlichen Intelligenzprogrammierung, und ich bin auf ein Problem gestoßen, das ich nicht alleine lösen kann (das ist meine Einführung in Lisp). Das Problem ist wirklich ein kleines, aber offensichtlich keines, das mein kleines Gehirn lösen kann.

Warum ist es so, dass es ein Fehler ist, wenn der Wert einer Funktion ein Lambda ist, diese Funktion aber als erstes Element einer Liste verwendet wird. Zum Beispiel:

Lisp:

(defun some-func ()
  #'(lambda (x) x))

;; Bei der REPL
;; Funktioniert nicht
> ((some-func) 1)
;; Funktioniert
> ((lambda (x) x) 1)
;; Funktioniert auch
> (funcall (some-func) 1)

Ich hoffe, das ergibt Sinn!

6voto

Eli Barzilay Punkte 28564

Dies ist eine gute Frage, und eine, bei der Common Lisp ziemlich verwirrend sein kann. Der Grund dafür ist, dass Common Lisp aus historischen Gründen zwei Namensräume hat - einen für Funktionen und einen für Werte. Um dies zu ermöglichen, gibt es zwei verschiedene Auswertungsregeln für die Kopfposition einer Funktionsanwendung und für den Rest - die erste bewertet ein Symbol als Funktionsnamen, und die zweite bewertet ein Symbol als Variablenreferenz. Es gibt natürlich Fälle, in denen der Wert tatsächlich eine Funktion ist - zum Beispiel, wenn Sie eine mapcar-Funktion schreiben, möchten Sie etwas wie das Folgende tun:

(defun my-mapcar (f l)
  (if (null l)
    '()
    (cons (f (car l)) (my-mapcar f (cdr l)))))

aber das wird nicht funktionieren - es wird sich über f als unbekannte Funktion beschweren. Für diese Fälle gibt es eine spezielle Funktion namens funcall, die eine Funktion und ein Argument für die Funktion empfängt und die Funktion wie üblich anwendet - und da funcall eine einfache Funktion ist, werden ihre Argumente wie üblich bewertet (als Werte). Also sollte das obige durch Verwendung von funcall behoben werden:

(defun my-mapcar (f l)
  (if (null l)
    '()
    (cons (funcall f (car l)) (my-mapcar f (cdr l)))))

Wie Sie wahrscheinlich jetzt vermuten, gibt es die gegenteiligen Fälle - wenn Sie etwas als Funktion evaluieren möchten und nicht als Wert. Zum Beispiel funktioniert dies nicht:

(my-mapcar 1+ '(1 2 3))

weil es auf die 1+-Variable verweist und nicht auf die Funktion. Für diese Fälle gibt es eine spezielle Form namens function, die ihren Inhalt als Funktion auswertet und ihn als Wert zurückgibt:

(my-mapcar (function 1+) '(1 2 3))

und es kann mit #' abgekürzt werden:

(my-mapcar #'1+ '(1 2 3))

Das ist nicht das Ende dieser Geschichte - um ein paar Beispiele zu nennen:

  • In einigen Fällen kann ein einfacher zitierter Name als Funktion funktionieren - zum Beispiel funktioniert '1+ im letzten Beispiel - aber dies ist eine Art Hack, der nur global gebundene Namen sehen kann, daher ist #' fast immer besser

  • Ein ähnlicher Hack kann mit lambda verwendet werden - also können Sie (my-mapcar '(lambda (x) (1+ x)) '(1 2 3)) verwenden, tatsächlich könnten Sie (list 'lambda '(x) '(1+ x)) verwenden, was sogar noch schlimmer ist (und meines Wissens nicht tragbar), aber die Verwendung von (lambda (x) (1+ x)) funktioniert, da es implizit in ein #' verpackt ist (versuchen Sie, eine lambda-Form als Makro zu erweitern, und Sie werden es sehen). Ein verwandter Hack macht es in Ordnung, einen lambda-Ausdruck als Kopf einer Funktionsanwendung zu verwenden (was eine der Dinge ist, die Sie versucht haben).

  • let etc. binden lokale Werte, und in einigen Fällen möchten Sie lokale Funktionen binden - dafür gibt es neue Bindungskonstrukte: flet und labels

Wenn all dies seltsam und/oder übermäßig kompliziert aussieht, dann sind Sie nicht allein. Es ist einer der Hauptunterschiede zwischen Common Lisp und Scheme. (Der Unterschied führt dann zu Änderungen in gängigen Idiomen in beiden Sprachen: Schemecode neigt dazu, wesentlich häufiger höhere Ordnungsfunktionen zu verwenden als Common Lisp-Code. Wie üblich bei diesen religiösen Fragen argumentieren einige Leute zugunsten dessen, was CL macht, und behaupten, dass höhere Ordnungsfunktionen verwirrend sind, deshalb mögen sie die explizite Erinnerung im Code.)

4voto

Xach Punkte 11197

((lambda (x) ...) ...) ist ein fest kodierter Spezialfall in den Regeln des Evaluators. Es wird das erste Element im Ausdruck nicht auswerten und das Ergebnis als allgemeinen Fall verwenden. Es ist normal, dass Sie funcall oder apply verwenden müssen, um eine Funktion aufzurufen, die das Ergebnis der Auswertung eines anderen Ausdrucks ist.

2voto

Terje Norderhaug Punkte 3559

Der Grund, warum Common Lisp es nicht erlaubt, dass eine Funktion, die ein Lambda zurückgibt, das erste Element in einem Ausdruck ist, hat mit der Unterscheidung zwischen Lisp-1 und Lisp-2 zu tun.

Wenn Common Lisp ((some-func) 1) erlauben würde, äquivalent zu (funcall (some-func) 1) zu sein, könnte dies als inkonsistent wahrgenommen werden, um zum Beispiel auch (let ((f #'some-func)) (f 1)) anstatt von (funcall f 1) zu erfordern.

1voto

Matthias Benkard Punkte 15177

Es gibt tatsächlich eine sehr gute Rechtfertigung dafür, solche Formen nicht zu unterstützen: Sie sind mehrdeutig. Betrachten Sie die Art und Weise, wie Funktionsnamen in Common Lisp definiert sind:

Funktionsname n. 1. (in einer Umgebung) Ein Symbol oder eine Liste (setf symbol), die der Name einer Funktion in dieser Umgebung ist. 2. Ein Symbol oder eine Liste (setf symbol).

Angesichts dessen, wie sollte sich eine Form wie die folgende verhalten?

((setf car) 10 x)

Sollte dies das x car auf den Wert 10 setzen oder sollte es die Form (setf car) ausführen (was ein Fehler wäre) und versuchen, den Rückgabewert als eine Funktion zu nutzen, die mit den Argumenten 10 und x aufgerufen werden soll? Common Lisp spezifiziert kein Verhalten und es ist überhaupt nicht klar, dass das eine schlechte Wahl ist. Schließlich sehe ich nichts im Standard, das konforme Implementierungen daran hindert, die Definition gültiger Formen zu erweitern, um eine breitere Palette von Operatornamen zu unterstützen (also würde es hier auch nicht wirklich helfen, setf-Funktionen speziell zu behandeln).

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X