เหตุใดผู้ประเมินที่เหมาะสม opt-calculus จึงสามารถคำนวณเลขชี้กำลังแบบโมดูลาร์ขนาดใหญ่โดยไม่ต้องใช้สูตรได้


137

หมายเลขคริสตจักรเป็นการเข้ารหัสตัวเลขธรรมชาติเป็นฟังก์ชัน

(\ 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

อย่างเรียบร้อยคุณสามารถยกกำลัง 2 หมายเลขคริสตจักรได้โดยใช้มัน นั่นคือถ้าคุณใช้ 4-2 คุณจะได้รับจำนวนคริสตจักรหรือ16 2^4เห็นได้ชัดว่านั่นเป็นเรื่องที่ไม่สามารถเกิดขึ้นได้จริง จำนวนคริสตจักรต้องการหน่วยความจำเชิงเส้นและช้ามากจริงๆ การประมวลผลสิ่ง10^10ที่ GHCI ตอบได้อย่างรวดเร็วจะใช้เวลานานและไม่สามารถใส่หน่วยความจำบนคอมพิวเตอร์ของคุณได้

ฉันได้ทำการทดลองกับผู้ประเมินที่เหมาะสมที่สุดเมื่อเร็ว ๆ นี้ ในการทดสอบของฉันฉันบังเอิญพิมพ์สิ่งต่อไปนี้ใน calculator-Calculator ที่ดีที่สุดของฉัน:

10 ^ 10 % 13

มันควรจะเป็นการคูณไม่ใช่การยกกำลัง ก่อนที่ฉันจะขยับนิ้วเพื่อยกเลิกโปรแกรมที่ทำงานตลอดไปด้วยความสิ้นหวังมันตอบคำขอของฉัน:

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

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

เมื่อ "การแจ้งเตือนข้อบกพร่อง" ของฉันกะพริบฉันจึงไปที่ Google และได้รับการยืนยัน10^10%13 == 3แล้ว แต่เครื่องคิดเลขλไม่ควรจะหาผลลัพธ์นั้นมันแทบจะไม่สามารถเก็บ 10 ^ 10 ได้ ฉันเริ่มเครียดสำหรับวิทยาศาสตร์ มันทันทีตอบฉัน20^20%13 == 3, ,50^50%13 == 4 60^60%3 == 0ฉันต้องใช้เครื่องมือภายนอกเพื่อตรวจสอบผลลัพธ์เหล่านั้นเนื่องจากHaskell เองไม่สามารถคำนวณได้ (เนื่องจากจำนวนเต็มล้น) (แน่นอนว่าถ้าคุณใช้จำนวนเต็มไม่ใช่ Ints!) ผลักดันให้ถึงขีด จำกัด นี่คือคำตอบของ200^200%31:

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

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

ถ้าเรามีจักรวาลหนึ่งสำเนาสำหรับแต่ละอะตอมในจักรวาลและเรามีคอมพิวเตอร์สำหรับแต่ละอะตอมที่เรามีทั้งหมดเราไม่สามารถจัดเก็บหมายเลขคริสตจักร200^200ได้ สิ่งนี้ทำให้ฉันตั้งคำถามว่า mac ของฉันมีประสิทธิภาพขนาดนั้นจริงๆหรือ บางทีผู้ประเมินที่เหมาะสมที่สุดก็สามารถข้ามสาขาที่ไม่จำเป็นและมาถึงคำตอบได้ในแบบเดียวกับที่ Haskell ทำด้วยการประเมินแบบขี้เกียจ เพื่อทดสอบสิ่งนี้ฉันรวบรวมโปรแกรมλไปที่ Haskell:

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)

สิ่งนี้ได้ผลลัพธ์ที่ถูกต้อง1( 5 ^ 5 % 4) - แต่โยนอะไรไว้ข้างบน10^10แล้วมันจะค้างทำให้กำจัดสมมติฐานออกไป

ตัวประเมินที่ดีที่สุดที่ฉันใช้คือโปรแกรม JavaScript ยาว 160 บรรทัดที่ไม่ได้เพิ่มประสิทธิภาพซึ่งไม่มีคณิตศาสตร์โมดูลัสเอกซ์โพเนนเชียลใด ๆ และฟังก์ชันโมดูลัสแลมดูลัสแคลคูลัสที่ฉันใช้ก็ง่ายพอ ๆ กัน:

(λab.(b(λcd.(c(λe.(d(λfg.(f(efg)))e))))(λc.(c(λde.e)))(λc.(a(b(λdef.(d(λg.(egf))))(λd.d)(λde.(ed)))(b(λde.d)(λd.d)(λd.d))))))

ฉันไม่ได้ใช้อัลกอริทึมหรือสูตรเลขคณิตแบบแยกส่วนเฉพาะ ดังนั้นผู้ประเมินที่เหมาะสมจะได้รับคำตอบที่ถูกต้องได้อย่างไร


2
คุณสามารถบอกเราเพิ่มเติมเกี่ยวกับประเภทของการประเมินที่เหมาะสมที่สุดที่คุณใช้? อาจเป็นกระดาษอ้างอิง? ขอบคุณ!
Jason Dagit

11
ฉันใช้อัลกอริทึมของนามธรรม Lamping ตามที่อธิบายไว้ในการดำเนินการที่เหมาะสมของการเขียนโปรแกรมภาษา Functionalหนังสือ สังเกตว่าฉันไม่ได้ใช้ "oracle" (ไม่มีครัวซองต์ / วงเล็บ) เนื่องจากคำนั้นสามารถพิมพ์ได้ EAL นอกจากนี้แทนที่จะลดแฟน ๆ แบบสุ่มในแบบคู่ขนานฉันจะสำรวจกราฟตามลำดับเพื่อที่จะไม่ลดโหนดที่ไม่สามารถเข้าถึงได้ แต่ฉันกลัวว่านี่จะไม่อยู่ในวรรณกรรม AFAIK ...
MaiaVictor

7
เอาล่ะในกรณีที่มีใครสงสัยฉันได้ตั้งค่าที่เก็บ GitHubด้วยซอร์สโค้ดสำหรับผู้ประเมินที่ดีที่สุดของฉัน มีความคิดเห็นมากมายและคุณสามารถทดสอบการทำงานnode test.jsได้ โปรดแจ้งให้เราทราบหากคุณมีคำถามใด ๆ
MaiaVictor

1
หาเรียบร้อย! ฉันไม่ทราบเพียงพอเกี่ยวกับการประเมินที่เหมาะสมที่สุด แต่ฉันสามารถพูดได้ว่านี่ทำให้ฉันนึกถึงทฤษฎีบทเล็กน้อยของแฟร์มาต์ / ทฤษฎีบทของออยเลอร์ หากคุณไม่รู้มันอาจเป็นจุดเริ่มต้นที่ดี
luqui

5
นี่เป็นครั้งแรกที่ฉันไม่ได้ระแคะระคายเลยว่าคำถามนี้เกี่ยวกับอะไร แต่ถึงกระนั้นก็ยังเพิ่มคะแนนให้กับคำถามและโดยเฉพาะอย่างยิ่งคำตอบแรกหลังที่โดดเด่น
Marco13

คำตอบ:


125

ปรากฏการณ์นี้มาจากจำนวนขั้นตอนการลดเบต้าที่ใช้ร่วมกันซึ่งอาจแตกต่างกันอย่างมากในการประเมินแบบขี้เกียจแบบ Haskell (หรือการเรียกตามค่าตามปกติซึ่งไม่ไกลขนาดนั้น) และใน Vuillemin-Lévy-Lamping- Kathail-Asperti-Guerrini- (et al …) การประเมินผล "เหมาะสมที่สุด" นี่เป็นคุณสมบัติทั่วไปที่ไม่ขึ้นอยู่กับสูตรเลขคณิตที่คุณสามารถใช้ในตัวอย่างนี้

การแบ่งปันหมายถึงการมีตัวแทนของคำแลมบ์ดาของคุณซึ่ง "โหนด" หนึ่งตัวสามารถอธิบายส่วนที่คล้ายกันหลายประการของคำแลมบ์ดาจริงที่คุณเป็นตัวแทน ตัวอย่างเช่นคุณสามารถแทนคำ

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

โดยใช้กราฟ (กำกับแบบสลับทิศทาง) ซึ่งมีเพียงการเกิดขึ้นเพียงครั้งเดียวของกราฟย่อยที่แสดง(\y.y)aและสองขอบที่กำหนดเป้าหมายกราฟย่อยนั้น ในแง่ของ Haskell คุณมีหนึ่ง thunk ที่คุณประเมินเพียงครั้งเดียวและสองคำชี้สำหรับ thunk นี้

การบันทึกช่วยจำแบบ Haskell ใช้การแบ่งปันข้อมูลย่อยที่สมบูรณ์ ระดับของการแบ่งปันนี้สามารถแสดงได้ด้วยกราฟอะไซคลิกที่กำหนดทิศทาง การแบ่งปันที่ดีที่สุดไม่มีข้อ จำกัด นี้: ยังสามารถแบ่งปันคำย่อย "บางส่วน" ซึ่งอาจบ่งบอกถึงวงจรในการแสดงกราฟ

หากต้องการดูความแตกต่างระหว่างการแบ่งปันทั้งสองระดับนี้ให้พิจารณาคำนี้

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

หากการแบ่งปันของคุณถูก จำกัด ให้ subterms สมบูรณ์ตามที่เป็นกรณีใน Haskell คุณอาจมีเพียงหนึ่งเกิด\z.zแต่ทั้งสองเบต้า redexes ที่นี่จะแตกต่าง: หนึ่ง(\z.z) xและคนอื่น ๆ ที่เป็น(\z.z) ((\z.z) x)และตั้งแต่พวกเขาจะไม่เท่าเทียมกัน ไม่สามารถแบ่งปันได้ หากอนุญาตให้มีการแบ่งปันคำย่อยบางส่วนก็เป็นไปได้ที่จะแบ่งปันคำบางส่วน(\z.z) [](ซึ่งไม่ใช่แค่ฟังก์ชัน\z.zแต่เป็น "ฟังก์ชันที่\z.zใช้กับบางสิ่งบางอย่าง ) ซึ่งประเมินในขั้นตอนเดียวเป็นเพียงบางสิ่งไม่ว่าอาร์กิวเมนต์นี้จะเป็นอย่างไร คุณสามารถมีกราฟที่มีเพียงโหนดเดียวเท่านั้นที่แสดงถึงสองแอปพลิเคชันของ\z.zเป็นสองอาร์กิวเมนต์ที่แตกต่างกันและซึ่งทั้งสองแอปพลิเคชันสามารถลดลงได้ในขั้นตอนเดียว โปรดสังเกตว่ามีวัฏจักรบนโหนดนี้เนื่องจากอาร์กิวเมนต์ของ "เหตุการณ์แรก" คือ "เหตุการณ์ที่สอง" อย่างแม่นยำ สุดท้ายด้วยการแบ่งปันที่ดีที่สุดคุณสามารถเปลี่ยนจาก (กราฟที่แสดงถึง) \x. (\z.z) ((\z.z) x))เป็น (กราฟที่แสดงถึง) ผลลัพธ์\x.xในขั้นตอนเดียวของการลดเบต้า (รวมถึงการทำบัญชีบางส่วน) นี่คือสิ่งที่เกิดขึ้นในตัวประเมินที่เหมาะสมที่สุดของคุณ (และการแสดงกราฟก็เป็นสิ่งที่ป้องกันการระเบิดในอวกาศด้วย)

สำหรับคำอธิบายเพิ่มเติมเล็กน้อยคุณสามารถดูเอกสารWeak Optimality และความหมายของการแบ่งปัน (สิ่งที่คุณสนใจคือบทนำและส่วน 4.1 และอาจมีคำแนะนำบรรณานุกรมบางส่วนในตอนท้าย)

กลับมาที่ตัวอย่างของคุณการเขียนโค้ดฟังก์ชันเลขคณิตที่ทำงานกับจำนวนเต็มของคริสตจักรเป็นหนึ่งในตัวอย่างเหมือง "ที่รู้จักกันดี" ซึ่งผู้ประเมินที่เหมาะสมสามารถทำงานได้ดีกว่าภาษากระแสหลัก (ในประโยคนี้ที่รู้จักกันดีจริง ๆ แล้วหมายความว่า ผู้เชี่ยวชาญตระหนักถึงตัวอย่างเหล่านี้) สำหรับตัวอย่างเพิ่มเติมโปรดดูเอกสารSafe Operators: Brackets Closed Foreverโดย Asperti และ Chroboczek (และโดยวิธีนี้คุณจะพบคำศัพท์แลมบ์ดาที่น่าสนใจที่ไม่สามารถพิมพ์ EAL ได้ดังนั้นฉันขอแนะนำให้คุณใช้ ดู oracles โดยเริ่มจากกระดาษ Asperti / Chroboczek)

อย่างที่คุณบอกว่าตัวเองการเข้ารหัสแบบนี้ใช้ไม่ได้จริง แต่ก็ยังเป็นวิธีที่ดีในการทำความเข้าใจสิ่งที่เกิดขึ้น และให้ฉันสรุปด้วยความท้าทายสำหรับการตรวจสอบเพิ่มเติม: คุณจะสามารถหาตัวอย่างได้หรือไม่ว่าการประเมินที่เหมาะสมที่สุดสำหรับการเข้ารหัสที่ไม่ดีเหล่านี้นั้นเทียบเท่ากับการประเมินแบบเดิมในการแสดงข้อมูลที่สมเหตุสมผลหรือไม่ (เท่าที่ฉันรู้นี่เป็นคำถามเปิดจริง)


35
นั่นเป็นโพสต์แรกที่ผิดปกติที่สุด ยินดีต้อนรับสู่ StackOverflow!
dfeuer

2
ไม่มีอะไรน้อยไปกว่าข้อมูลเชิงลึก ขอขอบคุณและยินดีต้อนรับสู่ชุมชน!
MaiaVictor

7

นี่ไม่ใช่ anwser แต่เป็นคำแนะนำว่าคุณจะเริ่มมองหาจุดไหน

มีวิธีง่ายๆในการคำนวณการยกกำลังแบบแยกส่วนในพื้นที่ขนาดเล็กโดยเฉพาะโดยการเขียนใหม่

(a * x ^ y) % z

เช่น

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

หากผู้ประเมินประเมินเช่นนี้และเก็บพารามิเตอร์การสะสมไว้aในรูปแบบปกติคุณจะหลีกเลี่ยงการใช้พื้นที่มากเกินไป แน่นอนถ้าประเมินของคุณเป็นที่ดีที่สุดแล้วสันนิษฐานว่าจะต้องไม่ทำงานใด ๆ มากกว่าหนึ่งนี้ดังนั้นโดยเฉพาะอย่างยิ่งไม่สามารถใช้พื้นที่มากขึ้นกว่าครั้งที่หนึ่งนี้จะใช้เวลาในการประเมิน

ฉันไม่แน่ใจจริงๆว่าผู้ประเมินที่เหมาะสมที่สุดคืออะไรดังนั้นฉันจึงกลัวว่าจะไม่สามารถทำให้เข้มงวดมากขึ้นได้


4
@Viclib Fibonacci ตามที่ @Tom กล่าวว่าเป็นตัวอย่างที่ดี fibต้องใช้เวลาเอกซ์โพเนนเชียลแบบไร้เดียงสาซึ่งสามารถลดลงเป็นเชิงเส้นได้ด้วยโปรแกรมบันทึกช่วยจำ / ไดนามิก แม้แต่เวลาลอการิทึม (!) ก็เป็นไปได้โดยการคำนวณกำลังเมทริกซ์ที่ n ของ[[0,1],[1,1]](ตราบใดที่คุณนับการคูณแต่ละครั้งเพื่อให้มีต้นทุนคงที่)
ไค

1
แม้เวลาคงที่ถ้าคุณกล้าพอที่จะประมาณ :)
J. Abrahamson

5
@TomEllis ทำไมบางสิ่งที่รู้วิธีลดนิพจน์แคลคูลัสแลมบ์ดาตามอำเภอใจจึงมีความคิดเช่นนั้น(a * b) % n = ((a % n) * b) % n? นั่นคือส่วนที่ลึกลับอย่างแน่นอน
Reid Barton

2
@ReidBarton แน่นอนฉันลอง! ผลลัพธ์เดียวกันแม้ว่า
MaiaVictor

2
@ TomEllis และ Chi มีเพียงคำพูดเล็ก ๆ น้อย ๆ ทั้งหมดนี้ถือว่าฟังก์ชันเรียกซ้ำแบบเดิมคือการใช้ fib แบบ "ไร้เดียงสา" แต่ IMO มีทางเลือกอื่นในการแสดงว่าเป็นธรรมชาติ รูปแบบปกติของการแสดงใหม่นั้นมีขนาดครึ่งหนึ่งของขนาดดั้งเดิม) และ Optlam ก็จัดการคำนวณแบบเชิงเส้น! ดังนั้นฉันขอยืนยันว่านั่นเป็นคำจำกัดความที่ "ไร้เดียงสา" ของ fib เท่าที่เกี่ยวข้องกับλ-calculus ฉันจะสร้างบล็อกโพสต์ แต่ไม่แน่ใจว่ามันคุ้มจริงๆ ...
MaiaVictor
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.