Frage

Während „The Seasoned Schemer“ Lesen Ich habe über letrec zu lernen begonnen. Ich verstehe, was es ist (mit einem Y-Combinator dupliziert werden), aber das Buch verwendet es statt dem auf Argumente auf den bereits defined Funktion Betriebswiederkehrende, die statisch bleiben.

Ein Beispiel für eine alte Funktion der defined Funktion auf sich wiederkehrende Verwendung (nichts Besonderes):

(define (substitute new old l)
  (cond
    ((null? l) '())
    ((eq? (car l) old)
      (cons new (substitute new old (cdr l))))
    (else
      (cons (car l) (substitute new old (cdr l))))))

Jetzt für ein Beispiel derselben Funktion aber mit letrec:

(define (substitute new old l)
  (letrec
    ((replace
      (lambda (l)
        (cond
          ((null? l) '())
          ((eq? (car l) old)
           (cons new (replace (cdr l))))
          (else
           (cons (car l) (replace (cdr l))))))))
(replace lat)))

Abgesehen von etwas länger sein und schwieriger Ich weiß nicht, zu lesen, warum sie Funktionen in dem Buch zu verwenden letrec neu schreiben. Gibt es eine Verbesserung Geschwindigkeit, wenn sie über eine statische Variable auf diese Weise wiederkehrender, weil Sie nicht vorbei halten es ??

Ist dies gängige Praxis für Funktionen mit Argumenten, die statisch bleiben, aber ein Argument, das reduziert wird (wie wiederkehrende Festlegung der Elemente einer Liste)?

Einige Eingabe von erfahrenen Schemers / LISPers würde helfen!

War es hilfreich?

Lösung

So Sie ein paar Antworten haben, die die Lesbarkeit Ausgabe zeigen, was in Ordnung sein sollte. Aber eine Frage, die unklar ist, ob es irgendwelche Performance-Probleme. Auf einem flachen Blick scheint es, dass die letrec Version, wie das namens let ein (das ist wirklich das gleiche ist) sollte schneller sein, da es weniger Argumente in der Schleife laufen um. Allerdings Compiler können in der Praxis hinter dem Rücken alle Arten von Optimierungen zu tun, wie Figur aus, dass die Schleife in der Ebene Version übergibt die ersten beiden Argumente unverändert, und es in eine einzigen Argumente Schleife mit dem ersten. Stattdessen zeigen Sie die Zahlen auf einem bestimmten System, hier ist ein PLT-Modul, dass Sie Zeit vier verschiedene Versionen laufen kann, und Sie können leicht weitere hinzufügen andere Variationen auszuprobieren. Die kurze Zusammenfassung ist, dass auf meiner Maschine, die Named-let Version ist etwas schneller, und es Schwanz-rekursive machen hat einen größeren Gesamtnutzen.

#lang scheme

;; original version
(define (substitute1 new old l)
  (cond [(null? l) '()]
        [(eq? (car l) old) (cons new (substitute1 new old (cdr l)))]
        [else (cons (car l) (substitute1 new old (cdr l)))]))

;; letrec version (implicitly through a named-let)
(define (substitute2 new old l)
  (let loop ([l l])
    (cond [(null? l) '()]
          [(eq? (car l) old) (cons new (loop (cdr l)))]
          [else (cons (car l) (loop (cdr l)))])))

;; making the code a little more compact
(define (substitute3 new old l)
  (let loop ([l l])
    (if (null? l)
      '()
      (cons (let ([fst (car l)]) (if (eq? fst old) new fst))
            (loop (cdr l))))))

;; a tail recursive version
(define (substitute4 new old l)
  (let loop ([l l] [r '()])
    (if (null? l)
      (reverse r)
      (loop (cdr l)
            (cons (let ([fst (car l)]) (if (eq? fst old) new fst)) r)))))

;; tests and timings

(define (rand-list n)
  (if (zero? n) '() (cons (random 10) (rand-list (sub1 n)))))

(for ([i (in-range 5)])
  (define l   (rand-list 10000000))
  (define new (random 10))
  (define old (random 10))
  (define-syntax-rule (run fun)
    (begin (printf "~a: " 'fun)
           (collect-garbage)
           (time (fun new old l))))
  ;; don't time the first one, since it allocates a new list to use later
  (define new-list (substitute1 new old l))
  (unless (and (equal? (run substitute1) new-list)
               (equal? (run substitute2) new-list)
               (equal? (run substitute3) new-list)
               (equal? (run substitute4) new-list))
    (error "poof"))
  (newline))

Andere Tipps

Sie spezifisches Beispiel Bezug: Persönlich finde ich die letrec Version leichter zu lesen: Sie definieren eine rekursive Hilfsfunktion und Sie nennen es in dem Körper der Top-Level-Funktion. Der wesentliche Unterschied zwischen den beiden Formen ist, dass Sie in der letrec Form nicht die statischen Argumente immer und immer wieder in den rekursiven Aufrufen angeben müssen, was ich sauberer zu finden sein.

Wenn der Code kompiliert wird, auf jeden rekursiven Funktionsaufruf das Überschreiten der statischen Argumente vermieden wird wahrscheinlich auch einen kleinen Leistungsvorteil in diesem Fall, da die Anrufer vermeiden die Argumente in den neuen Stapelrahmen kopieren zu müssen. Wenn die rekursive Aufruffunktion in der Schwanzposition ist, könnte der Compiler klug genug sein, um zu vermeiden, die Argumente Kopieren auf dem Stapel immer und immer wieder.

ähnlich, wenn der Code interpretiert wird, wird weniger Argumente in dem rekursiven Aufruf mit schneller sein.

Generell einer der wichtigsten Vorteile von letrec, das glaube ich nicht, dass Sie oben erwähnt, ist die Tatsache, dass es für beide Seiten rekursive Funktionsdefinitionen ermöglicht. Ich bin ziemlich unerfahren mit Schema, aber soweit ich das verstehe, ist dies eines der wichtigsten Merkmale der letrec Form im Vergleich zu z.B. define.

Für eine Sache, die letrec Version ermöglicht es Ihnen, die Funktion zu verwenden, auch wenn seine globalen Namen wieder auf etwas anderes ist, z.

(define substitute
  ; stuff involving letrec
  )

(define sub substitute)
(set! substitute #f)

Dann sub wird immer noch funktionieren, während es wäre nicht mit der Nicht-letrec Version.

Was der Leistung und Lesbarkeit, ist letzteres wahrscheinlich eine Frage des Geschmacks, während erstere nicht beobachtbar unterscheiden soll (obwohl ich nicht wirklich qualifiziert bin, darauf zu bestehen, auf diese so zu sein, und es ist abhängig von der Implementierung ohnehin auch).

Auch würde ich tatsächlich genannt let persönlich benutzen:

(define (substitute new old lat) ; edit: fixed this line
  (let loop (
             ; whatever iteration variables are needed + initial values
            )
    ; whatever it is that substitute should do at each iteration
    ))

Ich finde es auf diese Weise mehr lesbar. YMMV.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top