Bu, özyinelemeli yordamı kuyruk özyinelemeye dönüştürmenin genel bir yolu mu?


13

Görünüşe göre herhangi bir özyinelemeli işlemi kuyruk özyinelemeye dönüştürmek için genel bir yol buldum :

  1. Ek bir "sonuç" parametresi ile bir yardımcı alt prosedür tanımlayın.
  2. Prosedürün dönüş değerine uygulanacakları bu parametreye uygulayın.
  3. Başlamak için bu yardımcı prosedürü çağırın. "Sonuç" parametresinin başlangıç ​​değeri, özyinelemeli işlemin çıkış noktası değeridir, böylece elde edilen yinelemeli işlem özyinelemeli işlemin küçülmeye başladığı yerden başlar.

Örneğin, dönüştürülecek orijinal özyinelemeli yordam aşağıdadır ( SICP alıştırması 1.17 ):

(define (fast-multiply a b)
  (define (double num)
    (* num 2))
  (define (half num)
    (/ num 2))
  (cond ((= b 0) 0)
        ((even? b) (double (fast-multiply a (half b))))
        (else (+ (fast-multiply a (- b 1)) a))))

İşte dönüştürülmüş, kuyruk özyinelemeli prosedür ( SICP alıştırması 1.18 ):

(define (fast-multiply a b)
  (define (double n)
    (* n 2))
  (define (half n)
    (/ n 2))
  (define (multi-iter a b product)
    (cond ((= b 0) product)
          ((even? b) (multi-iter a (half b) (double product)))
          (else (multi-iter a (- b 1) (+ product a)))))
  (multi-iter a b 0))

Birisi bunu ispatlayabilir veya çürütebilir mi?


1
O(logn)

İkinci düşünce: b2'nin gücü olmayı seçmek , başlangıçta product0'a ayarlamanın tam olarak doğru olmadığını gösterir ; ancak 1 olarak değiştirmek bgarip olduğunda işe yaramaz . Belki 2 farklı akümülatör parametresine ihtiyacınız var?
j_random_hacker

3
Kuyruk olmayan özyinelemeli bir tanımın dönüşümünü gerçekten tanımladınız, bazı sonuç parametresi eklediniz ve bunu birikim için kullanmak oldukça belirsizdir ve örneğin iki özyinelemeli çağrınızın olduğu ağaç geçişleri gibi daha karmaşık vakalara zorlukla genellemezsiniz. Bununla birlikte, işin bir bölümünü yaptığınız ve daha sonra yaptığınız işi bir parametre olarak alarak bir "devam" işlevinin devralınmasına izin verdiğiniz daha kesin bir "devam" fikri vardır. Buna devam geçiş tarzı (cps) denir, bkz. En.wikipedia.org/wiki/Continuation-passing_style .
Ariel

4
Bu slaytlar fsl.cs.illinois.edu/images/d/d5/CS422-Fall-2006-13.pdf , içinde rastgele bir ifade aldığınız (muhtemelen kuyruk olmayan çağrılarla işlev tanımlarıyla) cps dönüşümünün bir açıklamasını içerir. ve sadece kuyruk çağrılarıyla eşdeğer bir ifadeye dönüştürmek.
Ariel

@j_random_hacker Evet, "dönüştürülmüş" prosedürümün aslında yanlış olduğunu görebiliyorum ...
nalzok

Yanıtlar:


12

Algoritmanızla ilgili açıklamanız bu noktada onu değerlendirmek için çok belirsiz. Ancak, göz önünde bulundurulması gereken bazı şeyler var.

CPS

Aslında, herhangi bir kodu sadece kuyruk çağrılarını kullanan bir forma dönüştürmenin bir yolu vardır . Bu CPS dönüşümüdür. CPS ( Devam-Geçiş Stili ), her işlevi bir devamı ileterek kod ifade etme biçimidir. Devam, "bir geri kalan hesaplamayı" temsil eden soyut bir kavramdır. CPS formunda ifade edilen kodda, bir sürekliliği yansıtmanın doğal yolu, değeri kabul eden bir işlevdir. CPS'de, değer döndüren bir işlev yerine, o andaki devamını temsil eden işlevi işlevin "döndürdüğü" duruma uygular.

Örneğin, aşağıdaki işlevi göz önünde bulundurun:

(lambda (a b c d)
  (+ (- a b) (* c d)))

Bu CPS'de şu şekilde ifade edilebilir:

(lambda (k a b c d)
  (- (lambda (v1)
       (* (lambda (v2)
            (+ k v1 v2))
          a b))
     c d))

Çirkin ve genellikle yavaş, ancak bazı avantajları var:

  • Dönüşüm tamamen otomatik olabilir. Bu yüzden kodu CPS formunda yazmaya (veya görmeye) gerek yoktur.
  • İle birlikte thunking ve tramplen , kuyruk çağrı optimizasyonu vermeyin dilde kuyruk çağrı optimizasyon sağlamak için kullanılabilir. (Doğrudan kuyruk özyinelemeli fonksiyonların kuyruk çağrısı optimizasyonu, özyinelemeli çağrıyı bir döngüye dönüştürmek gibi başka yollarla gerçekleştirilebilir. Ancak dolaylı özyineleme bu şekilde dönüştürmek kadar önemsiz değildir.)
  • CPS ile devamlar birinci sınıf bir nesne haline gelir. Devamlar kontrolün özü olduğundan, bu hemen hemen her kontrol operatörünün dilden herhangi bir özel desteğe ihtiyaç duymadan kütüphane olarak uygulanmasını sağlar. Örneğin, goto, istisnalar ve işbirlikçi diş çekme işlemleri, süreklilikler kullanılarak modellenebilir.

TCO

Bana öyle geliyor ki, kuyruk özyineleme (veya genel olarak kuyruk çağrıları) ile ilgili tek neden, kuyruk çağrısı optimizasyonu (TCO) amaçlıdır. Bu yüzden sormak daha iyi bir soru "benim kuyruk çağrı optimize edilebilir dönüşüm verim kodu mu?" Olduğunu düşünüyorum.

Bir kez daha CPS'yi ele alırsak, özelliklerinden biri, CPS'de ifade edilen kodun sadece kuyruk çağrılarından oluşmasıdır. Her şey bir kuyruk çağrısı olduğundan, yığına bir dönüş noktası kaydetmemiz gerekmez. Yani CPS formundaki tüm kodlar kuyruk çağrısı optimize edilmelidir , değil mi?

Pek iyi değil. Gördüğünüz gibi, yığını ortadan kaldırmış gibi görünsek de, yaptığımız tek şey onu temsil etme şeklimizi değiştirmek. Yığın şimdi bir süreyi temsil eden kapağın bir parçası. Bu nedenle CPS, tüm kod kuyruk çağrılarımızı sihirli bir şekilde optimize etmez.

Eğer CPS her şeyi TCO yapamazsa, özellikle doğrudan özyineleme için bir dönüşüm var mı? Hayır, genel olarak değil. Bazı özyinelemeler doğrusaldır, ancak bazıları değildir. Doğrusal olmayan (örneğin, ağaç) özyinelemelerin bir yerlerde değişken miktarda durumu koruması gerekir .


" TCO " alt bölümünde biraz kafa karıştırıcı , "kuyruk çağrısı optimize" derken aslında "sabit bellek kullanımı ile" demek. Dinamik bellek kullanımının sabit olmaması, çağrıların gerçekten kuyruk olduğu ve yığın kullanımında sınırsız bir büyüme olmadığı gerçeğini hala reddetmez . SICP bu tür hesaplamaları "yinelemeli" olarak nitelendiriyor, yani "TCO olmasına rağmen hala yinelemeli değil" demek daha iyi bir ifade olabilir (benim için).
Ness Ness

@WillNess Hala bir çağrı yığını var, sadece farklı temsil ediliyor. Yapı, donanım yığını yerine yığını kullandığımız için değişmez . Sonuçta, adlarında "yığın" olan dinamik yığın belleğe dayanan birçok veri yapısı vardır.
Nathan Davis

buradaki tek nokta, bazı dillerin çağrı yığınını kullanmak için kablolu limitleri olmasıdır.
Ness
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.