Λ-calculus optimal değerlendiricileri neden formülsüz büyük modüler üsleri hesaplayabiliyor?


135

Kilise sayıları doğal sayıların fonksiyon olarak kodlanmasıdır.

(\ f x  (f x))             -- church number 1
(\ f x  (f (f (f x))))     -- church number 3
(\ f x  (f (f (f (f x))))) -- church number 4

Düzgünce, sadece uygulayarak 2 kilise sayısını üs alabilirsiniz. Yani, 4 ila 2 uygularsanız, kilise numarasını alırsınız 16veya 2^4. Açıkçası, bu tamamen pratik değildir. Kilise sayılarının doğrusal bir hafızaya ihtiyacı vardır ve gerçekten çok yavaştır. 10^10GHCI'nin hızlı bir şekilde doğru cevapladığı gibi bir şey hesaplamak uzun zaman alır ve yine de bilgisayarınızdaki belleği sığdıramaz.

Son zamanlarda optimal λ değerlendiricileri ile deneyler yapıyorum. Testlerimde, optimum λ-hesap makineme yanlışlıkla aşağıdakileri yazdım:

10 ^ 10 % 13

Üstel değil çarpma olması gerekiyordu. Çaresizce sonsuza dek çalışan programı iptal etmek için parmaklarımı hareket ettirmeden önce, isteğimi yanıtladı:

3
{ iterations: 11523, applications: 5748, used_memory: 27729 }

real    0m0.104s
user    0m0.086s
sys     0m0.019s

Benim "hata uyarısı" yanıp sönerken, Google'a gittim ve 10^10%13 == 3gerçekten doğruladım . Ancak λ-hesaplayıcının bu sonucu bulması beklenmiyordu, 10 ^ 10'u zar zor saklayabiliyordu. Bilim için vurgulamaya başladım. Bu beni anında cevap 20^20%13 == 3, 50^50%13 == 4, 60^60%3 == 0. Haskell'in kendisi (tamsayı taşması nedeniyle) hesaplayamadığı için bu sonuçları doğrulamak için harici araçlar kullanmak zorunda kaldım (elbette Ingers değil Ingers kullanıyorsan!). Sınırlarını zorlayan, bunun cevabı şuydu :200^200%31

5
{ iterations: 10351327, applications: 5175644, used_memory: 23754870 }

real    0m4.025s
user    0m3.686s
sys 0m0.341s

Evrendeki her atom için evrenin bir kopyasına sahip olsaydık ve toplamda sahip olduğumuz her atom için bir bilgisayarımız olsaydı, kilise numarasını saklayamazdık 200^200. Bu, mac'umun gerçekten güçlü olup olmadığını sorgulamamı sağladı. Belki de optimal değerlendirici, gereksiz dalları atlayıp Haskell'in tembel değerlendirmeyle aynı şekilde cevabına varmayı başardı. Bunu test etmek için λ programını Haskell'e derledim:

data Term = F !(Term -> Term) | N !Double
instance Show Term where {
    show (N x) = "(N "++(if fromIntegral (floor x) == x then show (floor x) else show x)++")";
    show (F _) = "(λ...)"}
infixl 0 #
(F f) # x = f x
churchNum = F(\(N n)->F(\f->F(\x->if n<=0 then x else (f#(churchNum#(N(n-1))#f#x)))))
expMod    = (F(\v0->(F(\v1->(F(\v2->((((((churchNum # v2) # (F(\v3->(F(\v4->(v3 # (F(\v5->((v4 # (F(\v6->(F(\v7->(v6 # ((v5 # v6) # v7))))))) # v5))))))))) # (F(\v3->(v3 # (F(\v4->(F(\v5->v5)))))))) # (F(\v3->((((churchNum # v1) # (churchNum # v0)) # ((((churchNum # v2) # (F(\v4->(F(\v5->(F(\v6->(v4 # (F(\v7->((v5 # v7) # v6))))))))))) # (F(\v4->v4))) # (F(\v4->(F(\v5->(v5 # v4))))))) # ((((churchNum # v2) # (F(\v4->(F(\v5->v4))))) # (F(\v4->v4))) # (F(\v4->v4))))))) # (F(\v3->(((F(\(N x)->F(\(N y)->N(x+y)))) # v3) # (N 1))))) # (N 0))))))))
main = print $ (expMod # N 5 # N 5 # N 4)

Bu doğru çıktı 1( 5 ^ 5 % 4) - ama yukarıda herhangi bir şey atmak 10^10ve sıkışmış olacak, hipotezi ortadan kaldıracak.

Kullandığım optimal değerlendirici , herhangi bir üstel modül matematiği içermeyen 160 satır uzunluğunda, optimize edilmemiş bir JavaScript programı - ve kullandığım lambda-calculus modülüs işlevi de aynı derecede basitti:

ab.(bcd.(ce.(dfg.(f(efg)))e))))(λc.(cde.e)))(λc.(a(bdef.(dg.(egf))))(λd.d)(λde.(ed)))(bde.d)(λd.d)(λd.d))))))

Spesifik modüler aritmetik algoritma veya formül kullanmadım. Peki, optimal değerlendirici doğru cevaplara nasıl ulaşabilir?


2
Kullandığınız optimum değerlendirme türü hakkında bize daha fazla bilgi verebilir misiniz? Belki bir kağıt alıntısı? Teşekkürler!
Jason Dagit

11
Fonksiyonel Programlama Dillerinin En İyi Uygulanması kitabında açıklandığı gibi Lamping'in soyut algoritmasını kullanıyorum . Dikkat "oracle" (kruvasan / parantez yok) kullanmıyorum çünkü bu terim EAL-yazılabilir. Ayrıca, fanları paralel olarak rastgele azaltmak yerine, ulaşılamayan düğümleri azaltmamak için sırayla grafiği
geçiyorum

7
Herkesin merak etmesi durumunda, optimal değerlendiricimin kaynak kodunu içeren bir GitHub deposu oluşturdum. Birçok yorumu var ve çalışmasını test edebilirsiniz node test.js. Herhangi bir sorunuz varsa bize bildirin.
MaiaVictor

1
Düzgün bulmak! Optimal değerlendirme hakkında yeterince bilgim yok, ama bunun bana Fermat'ın Küçük Teoremi / Euler Teoremi'ni hatırlattığını söyleyebilirim. Farkında değilseniz, iyi bir başlangıç ​​noktası olabilir.
luqui

5
Bu, sorunun neyle ilgili olduğu hakkında en ufak bir fikre sahip olmadığım ilk kez, ancak yine de soruyu ve özellikle de ilk cevap sonrası cevabı iptal ediyor.
Marco13

Yanıtlar:


124

Bu fenomen, Haskell tarzı tembel değerlendirmede (veya bu açıdan çok uzak olmayan her zamanki değer değeri) ve Vuillemin-Lévy-Lamping- Kathail-Asperti-Guerrini- (et al…) "optimal" değerlendirme. Bu, bu özel örnekte kullanabileceğiniz aritmetik formüllerden tamamen bağımsız olan genel bir özelliktir.

Paylaşma, bir "düğümün" temsil ettiğiniz gerçek lambda teriminin birkaç benzer bölümünü tanımlayabileceği lambda teriminizin bir temsiline sahip olmak anlamına gelir. Örneğin, terimi

\x. x ((\y.y)a) ((\y.y)a)

içinde bir alt yöntemin temsil ettiği sadece bir oluşumun ve bu alt-hattı (\y.y)ahedefleyen iki kenarın bulunduğu bir (yönlendirilmiş asiklik) grafik kullanarak . Haskell terimleriyle, sadece bir kez değerlendirdiğiniz bir thunk ve bu thunk'a iki işaretiniz var.

Haskell tarzı notlama, tüm subtermlerin paylaşımını gerçekleştirir. Bu paylaşım seviyesi, yönlendirilmiş asiklik grafikler ile temsil edilebilir. En iyi paylaşım bu kısıtlamaya sahip değildir: grafik gösteriminde döngüler anlamına gelebilecek "kısmi" alt terimleri de paylaşabilir.

Bu iki paylaşım düzeyi arasındaki farkı görmek için,

\x. (\z.z) ((\z.z) x)

Paylaşımınız Haskell'de olduğu gibi alt dönemleri tamamlamakla sınırlıysa, yalnızca bir kez olabilir \z.z, ancak buradaki iki beta-redex farklı olacaktır: biri (\z.z) xve diğeri (\z.z) ((\z.z) x)eşittir ve bunlar eşit terimler olmadığından paylaşılamazlar. Kısmi alt öykülerin paylaşılmasına izin verilirse, bu argüman ne olursa olsun, bir adımda sadece bir şeye değerlendirilen kısmi terimi (\z.z) [](bu sadece işlev değil \z.z, "bir şeye\z.z uygulanan işlev ) paylaşmak mümkün olur . yalnızca bir düğümün iki uygulamayı temsil ettiği bir grafiğiniz olabilir.\z.zve bu iki uygulamanın sadece bir adımda azaltılabileceği iki farklı argümana. "İlk oluşum" argümanı tam olarak "ikinci oluşum" olduğundan, bu düğümde bir döngü olduğuna dikkat edin. Son olarak, en iyi paylaşımla \x. (\z.z) ((\z.z) x))sonucu \x.x(beta temsil eden bir grafik ) ' den (temsil eden bir grafik) sadece bir beta indirgeme adımına (artı bazı defter tutma) geçebilirsiniz. Temel olarak optimal değerlendiricinizde olan şey budur (ve grafik gösterimi de uzay patlamasını önleyen şeydir).

Hafifçe genişletilmiş açıklamalar için, Zayıf Optimallik ve Paylaşımın Anlamı makalesine bakabilirsiniz (ilgilendiğiniz giriş ve bölüm 4.1 ve belki de sondaki bibliyografik işaretçilerden bazılarıdır).

Örneğinize geri dönersek, Kilise tamsayıları üzerinde çalışan aritmetik fonksiyonların kodlanması, optimal değerlendiricilerin ana akım dillerden daha iyi performans gösterebildikleri örneklerin "iyi bilinen" madenlerinden biridir (bu cümlede, iyi bilinen aslında bir avuç uzmanlar bu örneklerin farkındadır). Bu tür daha fazla örnek için, Güvenli Operatörler: Asperti ve Chroboczek tarafından Sonsuza Kadar Kapalı Parantez'e bakın (ve bu arada, burada EAL tipi olmayan ilginç lambda terimlerini bulacaksınız; bu yüzden bu Asperti / Chroboczek gazetesinden başlayarak oracles'a bir bakış).

Kendinizi söylediğiniz gibi, bu tür kodlama tamamen pratik değildir, ancak hala neler olup bittiğini anlamanın güzel bir yolunu temsil eder. Ve daha ileri araştırmalar için bir meydan okuma ile bitireyim: Bu sözde kötü kodlamalar üzerinde optimal değerlendirmenin aslında makul bir veri sunumuna ilişkin geleneksel değerlendirmeyle eşit olduğu bir örnek bulabilecek misiniz? (bildiğim kadarıyla bu gerçekten açık bir soru).


34
Bu alışılmadık derecede kapsamlı bir ilk gönderi. StackOverflow'a hoş geldiniz!
dfeuer

2
Anlayıştan daha az bir şey yok. Teşekkürler ve topluluğa hoş geldiniz!
MaiaVictor

7

Bu bir anwser değil, nereye bakmaya başlayabileceğinizin bir önerisidir.

Çok az alandaki modüler üsleri hesaplamak için, özellikle de yeniden yazarak, önemsiz bir yol var.

(a * x ^ y) % z

gibi

(((a * x) % z) * x ^ (y - 1)) % z

Bir değerlendirici bu şekilde değerlendirir ve biriken parametreyi anormal biçimde tutarsa, çok fazla alan kullanmaktan kaçınacaksınız. Eğer gerçekten değerlendiriciniz optimal ise, muhtemelen bundan daha fazla iş yapmamalıdır, bu yüzden özellikle değerlendirmek için gereken süreden daha fazla alan kullanamazsınız.

Optimal bir değerlendiricinin gerçekten ne olduğundan gerçekten emin değilim, bu yüzden bunu daha titiz hale getiremiyorum.


4
@Tom'un dediği gibi @Viclib Fibonacci iyi bir örnek. fibnaif bir şekilde üstel zaman gerektirir, bu basit bir not / dinamik programlama ile doğrusal hale getirilebilir. Logaritmik (!) Zaman bile, n'inci matris gücünü hesaplayarak mümkündür [[0,1],[1,1]](her bir çarpımı saydığınız sürece sabit bir maliyete sahiptir).
chi

1
Yaklaşık olarak cesaret ediyorsanız sabit zaman bile :)
J. Abrahamson

5
@TomEllis Neden sadece keyfi lambda hesabı ifadelerinin nasıl azaltılacağını bilen bir şeyin böyle bir fikri var (a * b) % n = ((a % n) * b) % n? Bu kesinlikle gizemli kısım.
Reid Barton

2
@ReidBarton kesinlikle denedim! Yine de aynı sonuçlar.
MaiaVictor

2
@TomEllis ve Chi, Yine de küçük bir açıklama var. Her şey, geleneksel özyinelemeli fonksiyonun "saf" fib uygulaması olduğunu varsayar, ancak IMO, bunu ifade etmenin çok daha doğal bir alternatif yolu vardır. Bu yeni temsilin normal şekli geleneksel olanın yarısının büyüklüğündedir) ve Optlam bunu doğrusal olarak hesaplamayı başarır! Bu yüzden, fiberin λ-hesabı ile ilgili olarak "saf" tanımı olduğunu iddia ediyorum. Bir blog yazısı yaparım ama gerçekten buna değdiğinden emin değilim ...
MaiaVictor
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.