12

Ich habe Schwierigkeiten zu verstehen, was mit dem kleinen Schemer ist hier los ist evens-only*&co Beispiel auf Seite 145.The Little Schemer ausgleicht-only * & co

Hier ist der Code:

(define evens-only*&co 
(lambda (l col) 
    (cond 
    ((null? l) 
    (col '() 1 0)) 
    ((atom? (car l)) 
    (cond 
     ((even? (car l)) 
     (evens-only*&co (cdr l) 
        (lambda (newl product sum) 
         (col (cons (car l) newl) 
          (opx (car l) product) 
          sum)))) 
     (else 
     (evens-only*&co (cdr l) 
        (lambda (newl product sum) 
         (col newl product (op+ (car l) sum))))))) 
    (else 
    (evens-only*&co (car l) 
        (lambda (newl product sum) 
        (evens-only*&co (cdr l) 
            (lambda (dnewl dproduct dsum) 
             (col (cons newl dnewl) 
              (opx product dproduct) 
              (op+ sum dsum)))))))))) 

Die anfängliche col kann sein :

(define evens-results 
(lambda (newl product sum) 
    (cons sum (cons product newl)))) 

Was ich tue, ist, mit l als '((1) 2 3) nicht bin immer, geht es sofort in die endgültigen else mit (car l) als (1) und (cdr l) als (2 3). Gut, aber meine Meinung geht leer aus, indem ich versuche, die 10, dproduct, dsum aus der newl, product, sum auszusortieren. Es wäre auch hilfreich, wenn jemand mir beibringen könnte, wie man DrRacket oder Chez Scheme oder MIT-Scheme für den Betrieb eines Steppers einrichtet.

Aber vielleicht spaziere ich zu früh. Liest ein Anfänger, der das zum ersten Mal liest, eigentlich diese wilde Fortsetzung?

+0

Zum Einrichten der Stepper, siehe http://StackOverflow.com/Questions/10499514/Y-Combinator-Discussion-in-the-Little-Schemer/10500358#10500358 – soegaard

Antwort

16

Ich fand diesen Abschnitt auch beim ersten Lesen verwirrend, und fing erst an, es zu bekommen, nachdem ich woanders über Fortsetzungen und Fortsetzungsstil gelesen hatte (was das ist).

Auf die Gefahr der Erklärung etwas, das Sie bereits erhalten, eine Art zu sehen, die mir geholfen hat, ist zu denken, der "Sammler" oder "Fortsetzung" als den normalen Weg für die Funktion zu ersetzen, um Werte zurückzugeben. Im normalen Stil der Programmierung rufen Sie eine Funktion auf, erhalten einen Wert und tun etwas damit im Aufrufer. Zum Beispiel enthält die rekursive Standardfunktion length den Ausdruck (+ 1 (length (cdr list))) für den nicht leeren Fall. Das heißt, sobald (length (cdr list)) einen Wert zurückgibt, gibt es eine Berechnung, die darauf wartet, mit dem von ihr erzeugten Wert zu passieren, den wir uns als (+ 1 [returned value]) vorstellen könnten. Bei der normalen Programmierung verfolgt der Interpreter diese ausstehenden Berechnungen, die dazu neigen, sich zu "stapeln", wie Sie in den ersten paar Kapiteln des Buches sehen können. Zum Beispiel haben wir bei der rekursiven Berechnung der Länge einer Liste ein Nest von "Warteberechnungen" so viele Ebenen tief wie die Liste lang ist.

In Fortsetzung-Passing-Stil, sondern eine Funktion aufrufen und mit der Ergebnis in der aufrufenden Funktion zurück, sagen wir die Funktion, was zu tun ist, wenn es seinen Wert erzeugt, indem sie es mit einer „Fortsetzung“ Bereitstellung zu nennen. (Dies ähnelt dem, was Sie mit Callbacks in der asynchronen JavaScript-Programmierung tun müssen, zum Beispiel: Anstatt zu schreiben, schreiben Sie someFunction(function (result) { ... }), und der gesamte Code, der result verwendet, geht in die Callback-Funktion).

Hier ist length im Fortsetzungsmodus, nur zum Vergleich. Ich habe den Fortsetzungsparameter return aufgerufen, der vorschlagen sollte, wie es hier funktioniert, aber denken Sie daran, dass es nur eine normale Schema-Variable wie jede andere ist. (Oft wird der Fortsetzungsparameter in diesem Stil k genannt).

(define (length/k lis return) 
    (cond ((null? lis) (return 0)) 
     (else 
     (length/k (cdr lis) 
        (lambda (cdr-len) 
        (return (+ cdr-len 1))))))) 

Es gibt einen hilfreichen Tipp zum Lesen dieser Art von Code in an article on continuations by Little Schemer co-author Dan Friedman. (Siehe Abschnitt II-5 ab Seite 8).Paraphrasieren, ist hier, was die else Klausel oben sagt:

stellen Sie sich das Ergebnis des Aufrufs length/k auf (cdr lis) haben, und nennen es cdr-len, dann ein addieren und das Ergebnis dieser Addition auf Ihre Fortsetzung passieren (return) .

Beachten Sie, dass diese fast genau das, was der Dolmetscher bei der Bewertung (+ 1 (length (cdr lis))) in der normalen Version der Funktion zu tun hat (außer, dass es nicht (length (cdr lis)) einen Namen für das Zwischenergebnis zu geben hat. Indem um die Fortsetzungen oder Rückrufe Wir haben den Kontrollfluss (und die Namen der Zwischenwerte) explizit gemacht, anstatt dass der Interpreter den Überblick behält.

Wir wenden diese Methode auf jede Klausel in evens-only*&co an Tatsache, dass diese Funktion drei Werte statt eins erzeugt: die verschachtelte Liste mit ungeraden Zahlen entfernt; das Produkt der geraden Zahlen; und die Summe der ungeraden Zahlen. Hier ist die erste Klausel, wo (car l) bekannt ist, eine gerade Zahl zu sein:

(evens-only*&co (cdr l) 
       (lambda (newl product sum) 
        (col (cons (car l) newl) 
         (opx (car l) product) 
         sum))) 

Stellen Sie sich vor, dass Sie die Ergebnisse der Entfernung von ungeraden Zahlen haben, Multiplikation Evens, und das Hinzufügen von ungeraden Zahlen aus den cdr der Liste, und rufen Sie sie newl, product bzw. sum. cons der Kopf der Liste auf newl (da es eine gerade Zahl ist, sollte es im Ergebnis gehen); Multiplizieren Sie product mit dem Kopf der Liste (seit berechnen wir Produkt von Evens); Lassen Sie sum allein; und übergeben Sie diese drei Werte an Ihre wartende Fortsetzung col.

Hier ist der Fall, in dem der Kopf der Liste eine ungerade Zahl ist:

(evens-only*&co (cdr l) 
       (lambda (newl product sum) 
        (col newl product (op+ (car l) sum)))) 

Wie zuvor, aber die gleichen Werte von newl und product zur Fortsetzung (dh „Rückkehr“ sie) übergeben, zusammen mit der Summe von sum und dem Kopf der Liste, da wir ungerade Zahlen summieren.

Und hier ist das letzte, wo (car l) eine verschachtelte Liste ist, und die durch die doppelte Rekursion etwas kompliziert:

(evens-only*&co (car l) 
       (lambda (newl product sum) 
        (evens-only*&co (cdr l) 
            (lambda (dnewl dproduct dsum) 
            (col (cons newl dnewl) 
             (opx product dproduct) 
             (op+ sum dsum)))))) 

Stellen Sie die Ergebnisse aus dem Entfernen haben, Summieren und das Hinzufügen der Zahlen in (car l) und rufen Sie diese newl, product und sum; dann stellen Sie sich vor, Sie haben die Ergebnisse aus der gleichen Sache zu (cdr l), und rufen Sie sie dnewl, dproduct und dsum.Zu Ihrer wartenden Fortsetzung, geben Sie die Werte von cons ing newl und dnewl (da wir eine Liste von Listen erstellen); zusammen multiplizieren product und dproduct; und Hinzufügen von sum und dsum.

Hinweis: jedes Mal, wenn wir einen rekursiven Aufruf zu machen, haben wir eine neue Fortsetzung für den rekursiven Aufruf konstruieren, die „schließt sich über“ die aktuellen Werte des Arguments, l, und die Rückkehr Fortsetzung - col, mit anderen Worten Man kann sich die Kette von Fortsetzungen vorstellen, die wir während der Rekursion aufbauen, indem wir den "Call-Stack" einer konventioneller geschriebenen Funktion modellieren!

Hoffe, dass gibt einen Teil einer Antwort auf Ihre Frage. Wenn ich etwas über Bord gegangen bin, dann nur deshalb, weil ich dachte, dass nach der Rekursion selbst Fortsetzungen die zweite, wirklich nette, Gedanken erweiternde Idee in der Programmierung und im Allgemeinen sind.

+0

Danke Jon O. Ich bin immer noch verschwommen was passiert im dritten Fall, wo es eine verschachtelte Liste und kein Atom ist. Nehmen wir an, die Eingabeliste l ist ((2)), was bedeutet, dass sie zum ersten Mal den Fall 3 mit (2) für (auto l) und() für (cdr l) und meinen Evens-Ergebnissen für das col trifft - was dann zuerst nach unten geht, wiederholt sich. Erneutes Eingeben von even-only * & co gilt für den ersten cond, (null? L), und nimmt '(), 1, 0 für dnewl, dproduct, dsum auf. Aber dies ist das allererste Mal, wo vermutlich newl, product, sum nicht initialisiert wurden oder gar existieren. Aber offensichtlich sehe ich das falsch. . . . – melwasul

+0

@melwasul '((2))' wird übersetzt in 'col (cons (cons 2())()) (* (* 2 1) 1) (+ 0 0)'. –

0

Ich habe gelesen How To Design-Programme (felleisen et.al.). Ich gehe durch den Abschnitt, wo sie lokale Definitionen definieren. Ich habe einen Code geschrieben, der die oben genannten nur & co implementiert mit einer lokalen Definition. Hier ist, was ich schrieb:

(define (evens-only&co l) 
    (local ((define (processing-func sum prod evlst lst) 
      (cond ((null? lst) (cons sum (cons prod evlst))) 
        ((atom? (car lst)) 
        (cond ((even? (car lst)) (processing-func sum (* prod (car lst)) (append evlst (list (car lst))) (cdr lst))) 
         (else 
          (processing-func (+ sum (car lst)) prod evlst (cdr lst))))) 
        (else 
        (local ((define inner-lst (processing-func sum prod '() (car lst)))) 
        (processing-func (car inner-lst) (cadr inner-lst) (append evlst (list (cddr inner-lst))) (cdr lst))))))) 
    (processing-func 0 1 '() l))) 

Zum Testen, wenn ich eine (nivelliert-only & co '((9 1 2 8) 3 10 ((9 9) 7 6) 2)), es gibt' (38 1920 (2 8) 10 (() 6) 2) wie im kleinen Intriganten zu erwarten. Aber mein Code schlägt in einer Bedingung fehl: Wenn es überhaupt keine geraden Zahlen gibt, wird das Produkt von even immer noch als 1 angezeigt. Zum Beispiel (nur & co '((9 1) 3 ((9 9) 7))) gibt '(38 1() (())) zurück. Ich denke, ich brauche eine zusätzliche Funktion, um dies zu korrigieren. @melwasul: Wenn Sie nicht vertraut sind mit der lokalen Definition, tut uns leid, dies hier zu veröffentlichen. Ich schlage vor, dass Sie auch HTDP lesen. Es ist ein ausgezeichnetes Buch für Anfänger. Aber die Leute, die Experten im Schema sind, können bitte auch ihre Kommentare zu meinem Code posten. Ist mein Verständnis der lokalen Definition korrekt?

+0

In der Tat ist es korrekt, dass das Produkt "1" sein sollte, wenn es keine geraden Zahlen in der Liste gibt: das Produkt einer leeren Liste von Zahlen sollte die multiplikative Identität "1" sein, genauso wie die Summe einer leeren Liste ist die additive Identität, "0". Versuchen Sie zum Beispiel, '(*)' an einer Scheme-Eingabeaufforderung auszuwerten. –

+0

Sie haben Recht. Danke für die Klarstellung. –

1

Die answer von Jon O. ist eine sehr gute eingehende Erklärung der zugrunde liegenden Konzepte. Aber für mich (und hoffentlich auch für einige andere Leute) ist es viel einfacher, solche Konzepte zu verstehen, wenn sie eine visuelle Darstellung haben.

Also, ich habe setzen vorbereitet zwei Flussdiagrammen (ähnlich once I did for multirember&co, entwirren, was während des Gesprächs von evens-only*&co

wenn l ist passiert:

'((9 1 2 8) 3 10 ((9 9) 7 6) 2) 

und col ist:

(define the-last-friend 
    (lambda (newl product sum) 
     (cons sum (cons product newl)) 
    ) 
) 

Ein Flussdiagramm, das widerspiegelt, wie sich Variablen in diff Erent Schritte Rekursion: variable relations Zweite Flussdiagramm, die Istwerten zeigt, wobei bestanden: actual values

Meine Hoffnung ist, dass diese Antwort eine anständige Neben dem Jon's explanation above sein wird.

0

In equational Pseudo-Code (a KRC -ähnlichen Notation, Schreiben f x y für den Anruf (f x y), wo es eindeutig ist), dann ist dies

evens-only*&co l col 
    = col [] 1 0          , IF null? l 
    = evens-only*&co (cdr l) 
        (newl product sum => 
         col (cons (car l) newl) 
          (opx (car l) product) 
          sum)     , IF atom? (car l) && even? (car l) 
    = evens-only*&co (cdr l) 
        (newl product sum => 
         col newl product (op+ (car l) sum))  , IF atom? (car l) 
    = evens-only*&co (car l) 
        (anewl aproduct asum => 
         evens-only*&co (cdr l) 
             (dnewl dproduct dsum => 
              col (cons anewl dnewl) 
               (opx aproduct dproduct) 
               (op+ asum  dsum))) , OTHERWISE 

Dies ist ein CPS Code, der alle Evens vom Eingang verschachtelt Liste sammelt (dh ein Baum) unter Beibehaltung der Baumstruktur, und findet auch das Produkt aller Evens; wie für den nicht-Evens, es fasst sie:

  • wenn l eine leere Liste ist, die drei Grund (Identität) Werte werden als Argumente übergeben, bis Spalte;

  • wenn (car l) eine gerade Zahl ist, die Ergebnisse der Verarbeitung der (cdr l) sind newl, product und sum, und dann sie als Argumente an col übergeben werden, während die ersten beiden durch consing ergänzt werden   ⁄   mit der Multiplikation (car l) (die gerade Zahl);

  • wenn (car l) ist ein Atom, das nicht eine gerade Zahl ist, die die Ergebnisse der Verarbeitung (cdr l) sind newl, product und sum, und dann sie als Argument an col mit der dritten Augmented übergeben werden, indem sie mit der Summations (car l) (das nicht gerade Atom);

  • wenn (car l) ist eine Liste, die Ergebnisse der Verarbeitung der (car l) sind anewl, aproduct und asum, und dann die Ergebnisse der Verarbeitung der (cdr l) sind dnewl, dproduct und dsum, und dann die drei kombinierten Ergebnisse sind als Argumente an col übergeben.

[], 1 und 0 des Basisfall sind die Identitätselemente der monoids von Listen, Zahlen unter Multiplikation und Zahlen unter Zugabe sind. Dies bedeutet nur spezielle Werte, die das Ergebnis nicht verändern, wenn sie in das Programm integriert werden.

Als Illustration für '((5) 2 3 4) (die dem Beispiel in der Frage der Nähe ist), schafft es die Berechnung

evens-only*&co [[5], 2, 3, 4] col 
= 
col (cons []     ; original structure w only the evens kept in, 
      (cons 2    ; for the car and the cdr parts 
       (cons 4 []))) 
    (opx 1      ; multiply the products of evens in the car and 
      (opx 2 (opx 4 1))) ; in the cdr parts 
    (op+ (op+ 5 0)    ; sum, for the non-evens 
      (op+ 3 0))  

Ähnlich my other answer (mit einer Schwester Frage), hier ist eine andere Art und Weise zu dies schreibe, mit einem Rüttler-matching Pseudo-Code (mit Wachen):

evens-only*&co = g where 
    g [a, ...xs...] col 
     | pair? a = g a (la pa sa => 
         g xs (ld pd sd => 
             col [la, ...ld...] (* pa pd) (+ sa sd))) 
     | even? a = g xs (l p s => col [ a, ...l... ] (* a p)  s ) 
     | otherwise = g xs (l p s => col   l   p (+ a s) ) 
    g []   col =     col []    1   0 

Die Wirtschaft (und Vielfalt) dieser Notation macht es wirklich alle viel klarer, eas ier zu nur statt im Wort Salat von langen Namen für Funktionen und Variablen gleichermaßen verloren gehen, mit Parens als syntaktische Separatoren für Listendaten, Klausel Gruppierungen überladen (wie in cond Ausdrücke), Namensbindungen (in lambda Ausdrücke) und Funktionsaufruf Bezeichner alle suchen genau gleich. Die gleiche Gleichförmigkeit der S-Ausdrücke-Notation, die der Einfachheit der Manipulation durch eine Maschine (d. H. Lisp's und Makros) lisp ist, ist für die Lesbarkeit derselben von Nachteil.