2012-03-01 16 views
6

değişkeni Aşağıdaki kodu görüyorum ... (next-num) ilk çağrısı 1 değerini döndürür ve ikincisi 2 döndürür.Bir işlevde

(define next-num 
    (let ((num 0)) 
    (lambda() (set! num (+ num 1)) num))) 

(next-num) ; 1 
(next-num) ; 2 

şeması her zaman next-num denir biliyor ... Nasıl yerel bir değişkenin türüdür, numnext-num içindeki let tarafından oluşturulan ve ... anlayamıyorum Ne, num değeridir let ((num 0)) tarafından silinmemiştir; Şema her zaman next-num çağrıldığında değiştirdiğimiz her zaman aynı num olduğunu biliyor mu?

num'un hem yerel hem de statik olduğu anlaşılıyor ... Yerel bir değişkeni nasıl tanımlayabiliriz, ancak statik değil?

cevap

9

Bu "sözcüksel kapanma" dır ve siz doğru "", "kapalı-over değişkeni", C'deki bir statik değişkene benzer, örneğin: sadece let biçimindeki kodla görünür kapsamı "), ancak tüm program akışı boyunca, işleve yapılan her çağrıyla yeniden başlatılmak yerine, devam eder.

Sanırım şaşkın olduğunuz kısım şudur: "num, next-num içine girilerek oluşturulmuştur, bu bir yerel değişkendir." Bu, let bloğunun next-num işlevinin bir parçası olmadığı için doğru değil: aslında, next-num'a bağlı olan işlevi oluşturan ve döndüren bir ifade. (Bu çok farklıdır, örn., Fonksiyonların sadece derleme zamanında oluşturulabildiği ve bunları en üst düzeyde tanımlayabildiği C'den çok farklıdır. Şemada işlevler, tüm ifadelerin geri dönebileceği tamsayılar veya listeler gibi değerlerdir). arasındaki farkı hatırlamak

(define next-num #f) ; dummy value 
(let ((num 0)) 
    (set! next-num 
     (lambda() (set! num (+ num 1)) num))) 

Bu önemlidir: Burada

(neredeyse) define sadece bir işlev-dönen ifadenin değerine next-num ilişkilendirerek olduğunu daha açık hale getirir aynı şeyi yazmak için başka bir yolu

(define (some-var args ...) expression expression ...) 
tüm expressions çağrıldığında yürütür some-var bir işlev yapar

ve

'u expression değerine bağlayan

, oradan ve sonra değerlendirilmiştir. o lambda forma etrafında, lexically kapsamı değişkenin, num eklenmesiyle, bu hemen hemen aynı

(define some-var 
    (lambda (args ...) expression expression ...)) 

Kişisel koduna denk mi çünkü Açıkçası, eski sürüm, gereksizdir.

Son olarak, burada kapanma değişkenleri ve statik değişkenler arasındaki önemli bir fark vardır, bu da kapanmaları çok daha güçlü kılar.Eğer yazılı olsaydı yerine aşağıdaki:

(define f (make-next-num 7)) 
(define g (make-next-num 2)) 

(f) ; => 8 
(g) ; => 3 
(f) ; => 9 

Bu geçerli:

(define make-next-num 
    (lambda (num) 
    (lambda() (set! num (+ num 1)) num))) 

sonra make-next-num her çağrı bu fonksiyona özeldir ayrı ve yeni bir num değişkeni ile anonim bir işlev yaratacak Sözcüksel kapanışlarla dillerin gücünün büyük bir kısmını oluşturan gerçekten harika ve güçlü bir hile.

Eklemek için düzenlenmiştir: Şema "num'un next-num çağrıldığında nasıl değişiklik yapacağını" nasıl bildiğini "soruyorsunuz. Taslak halinde, eğer uygulamada değilse, bu aslında oldukça basittir. Şemadaki her ifade, değerleri tutabilecek yerlere isimlerin birlikleri olan değişken bağların bir ortamı (bir arama tablosu) bağlamında değerlendirilir. Bir let formunun veya bir işlev çağrısının her bir değerlendirmesi, mevcut ortamı yeni bağlamalar ile genişleterek yeni bir ortam oluşturur. lambda formlarının kapatılmasını sağlamak için uygulama, bunları, işlevin kendisinden ve tanımlandığı ortamdan oluşan bir yapı olarak temsil eder. Bu işleve yapılan çağrılar, işlevin tanımlandığı bağlama ortamını genişleterek değerlendirilir - çağrıldığı ortam.

Eski Lisps (yakın zamana kadar Emacs Lisp dahil) lambda'a sahipti, ancak sözcüksel kapsam değil, bu nedenle anonim işlevler oluşturabilseniz bile, bunlara yapılan çağrılar çağrı ortamından ziyade çağrı ortamında değerlendirilirdi ve kapanışları. Scheme'nin bu hakkı elde eden ilk dil olduğuna inanıyorum. Sussman ve Steele'in, şemanın uygulanmasına ilişkin orijinali Lambda Papers, diğer pek çok şeyin yanı sıra, kapsam belirleme anlamak isteyen herkes için büyük bir zihin genişletme okuması yapıyor.