เราทุกคนรู้ (หรือควรรู้) ว่า Haskell ขี้เกียจไปโดยปริยาย ไม่มีการประเมินผลจนกว่าจะต้องประเมิน
ไม่
Haskell ไม่ใช่ภาษาขี้เกียจ
Haskell เป็นภาษาที่ลำดับการประเมินไม่สำคัญเพราะไม่มีผลข้างเคียง
ไม่ใช่เรื่องจริงที่ว่าลำดับการประเมินจะไม่สำคัญเพราะภาษาอนุญาตให้มีการวนซ้ำแบบไม่สิ้นสุด หากคุณไม่ระมัดระวังอาจเป็นไปได้ที่จะติดอยู่ใน cul-de-sac ที่คุณประเมินนิพจน์ย่อยตลอดไปเมื่อคำสั่งการประเมินที่แตกต่างกันจะนำไปสู่การยุติในเวลาที่ จำกัด ดังนั้นจึงถูกต้องกว่าที่จะพูดว่า:
- การนำไปใช้งานของ Haskell จะต้องประเมินโปรแกรมในลักษณะที่ยุติหากมีคำสั่งการประเมินใด ๆ ที่ยุติลง เฉพาะในกรณีที่ทุกคำสั่งการประเมินที่เป็นไปได้ล้มเหลวในการยุติการใช้งานจะไม่สามารถยุติได้
สิ่งนี้ยังคงทำให้การใช้งานมีอิสระอย่างมากในการประเมินโปรแกรม
โปรแกรม Haskell คือการแสดงออกเดียวคือทั้งหมดผูกระดับบนสุดlet {
} in Main.main
การประเมินผลสามารถเข้าใจได้ว่าเป็นลำดับของขั้นตอนการลด (ขนาดเล็ก -) ซึ่งเปลี่ยนนิพจน์ (ซึ่งแสดงถึงสถานะปัจจุบันของโปรแกรมการดำเนินการ)
คุณสามารถแบ่งขั้นตอนการลดออกเป็นสองประเภท ได้แก่ ขั้นตอนที่จำเป็น (พิสูจน์ได้จะเป็นส่วนหนึ่งของลำดับการลดขั้นตอนการยุติ) และขั้นตอนที่ไม่ได้ คุณสามารถแบ่งการลดทอนที่จำเป็นชั่วคราวได้อย่างคลุมเครือออกเป็นสองประเภทย่อย ได้แก่ ประเภทที่ "จำเป็น" อย่างชัดเจนและประเภทที่ต้องการการวิเคราะห์ที่ไม่สำคัญเพื่อพิสูจน์ว่าจำเป็น
การดำเนินการลดเฉพาะที่จำเป็นอย่างชัดเจนคือสิ่งที่เรียกว่า "การประเมินแบบเกียจคร้าน" ฉันไม่รู้ว่าเคยมีการประเมินการใช้งาน Haskell แบบขี้เกียจอย่างหมดจดหรือไม่ กอดอาจเป็นหนึ่งเดียว GHC ไม่แน่นอน
GHC ดำเนินการลดขั้นตอนในเวลารวบรวมที่ไม่จำเป็นต้องพิสูจน์ได้ ตัวอย่างเช่นจะแทนที่1+2::Int
ด้วย3::Int
แม้ว่าจะไม่สามารถพิสูจน์ได้ว่าจะใช้ผลลัพธ์
นอกจากนี้ GHC ยังอาจทำการลดขนาดที่ไม่จำเป็นในขณะดำเนินการในบางสถานการณ์ ตัวอย่างเช่นเมื่อมีการสร้างรหัสในการประเมินf (x+y)
ถ้าx
และy
เป็นชนิดInt
และค่าของพวกเขาจะเป็นที่รู้จักในเวลาทำงาน แต่f
ไม่สามารถพิสูจน์การใช้อาร์กิวเมนต์ของมันมีเหตุผลที่จะไม่คำนวณไม่ก่อนที่จะเรียกx+y
f
ใช้พื้นที่ฮีปน้อยลงและพื้นที่โค้ดน้อยลงและอาจเร็วกว่าแม้ว่าจะไม่ได้ใช้อาร์กิวเมนต์ก็ตาม อย่างไรก็ตามฉันไม่รู้ว่า GHC ใช้โอกาสในการเพิ่มประสิทธิภาพประเภทนี้จริงหรือไม่
GHC ดำเนินการตามขั้นตอนการประเมินตามเวลาดำเนินการอย่างแน่นอนซึ่งพิสูจน์แล้วว่าจำเป็นโดยการวิเคราะห์ข้ามโมดูลที่ค่อนข้างซับซ้อนเท่านั้น นี่เป็นเรื่องปกติมากและอาจแสดงถึงการประเมินโปรแกรมที่เป็นจริงเป็นส่วนใหญ่ การประเมินความเกียจคร้านเป็นกลยุทธ์การประเมินทางเลือกสุดท้าย ไม่ใช่สิ่งที่เกิดขึ้นตามกฎ
มีสาขา"การประเมินในแง่ดี"ของ GHC ซึ่งทำการประเมินเชิงเก็งกำไรในขณะดำเนินการ ถูกทิ้งร้างเนื่องจากความซับซ้อนและภาระการบำรุงรักษาต่อเนื่องไม่ใช่เพราะทำงานได้ไม่ดี หาก Haskell ได้รับความนิยมเช่นเดียวกับ Python หรือ C ++ ฉันแน่ใจว่าจะมีการนำไปใช้กับกลยุทธ์การประเมินรันไทม์ที่ซับซ้อนมากขึ้นซึ่งดูแลโดย บริษัท ที่มีกระเป๋าลึก การประเมินโดยไม่ขี้เกียจไม่ได้เป็นการเปลี่ยนแปลงภาษา แต่เป็นเพียงความท้าทายด้านวิศวกรรม
การลดนั้นขับเคลื่อนโดย I / O ระดับบนสุดและไม่มีอะไรอื่น
คุณสามารถจำลองการโต้ตอบกับโลกภายนอกได้โดยใช้กฎการลดผลข้างเคียงแบบพิเศษเช่น "หากโปรแกรมปัจจุบันเป็นแบบฟอร์มgetChar >>= <expr>
ให้รับอักขระจากอินพุตมาตรฐานและลดโปรแกรมเพื่อ<expr>
ใช้กับอักขระที่คุณได้รับ"
return ()
เป้าหมายทั้งหมดของระบบเวลาทำงานคือการประเมินโปรแกรมจนกว่าจะมีหนึ่งในรูปแบบที่มีผลข้างเคียงเหล่านี้แล้วทำผลข้างเคียงจากนั้นทำซ้ำจนกว่าโปรแกรมที่มีรูปแบบบางส่วนที่แสดงถึงการเลิกจ้างเช่น
ไม่มีกฎเกณฑ์อื่นว่าอะไรลดเมื่อไหร่ มีเพียงกฎเกี่ยวกับสิ่งที่สามารถลดได้
ยกตัวอย่างเช่นกฎเฉพาะสำหรับif
การแสดงออกที่if True then <expr1> else <expr2>
สามารถลดลงได้<expr1>
, if False then <expr1> else <expr2>
สามารถลดลงได้<expr2>
และif <exc> then <expr1> else <expr2>
ที่<exc>
เป็นค่าพิเศษสามารถลดค่าพิเศษ
หากนิพจน์ที่แสดงสถานะปัจจุบันของโปรแกรมของคุณเป็นif
นิพจน์คุณไม่มีทางเลือกอื่นนอกจากดำเนินการลดเงื่อนไขจนกว่าจะเป็นTrue
หรือFalse
หรือ<exc>
เพราะนั่นเป็นวิธีเดียวที่คุณจะกำจัดif
นิพจน์และมีความหวังว่าจะไปถึง สถานะที่ตรงกับกฎ I / O ข้อใดข้อหนึ่ง แต่ข้อกำหนดทางภาษาไม่ได้บอกให้คุณทำแบบนั้นในหลาย ๆ คำ
ข้อ จำกัด ในการสั่งซื้อโดยปริยายประเภทนี้เป็นวิธีเดียวที่การประเมินสามารถ "บังคับ" ให้เกิดขึ้นได้ นี่เป็นแหล่งที่มาของความสับสนสำหรับผู้เริ่มต้นบ่อยๆ ยกตัวอย่างเช่นบางคนพยายามที่จะทำให้foldl
เข้มงวดมากขึ้นโดยการเขียนแทนfoldl (\x y -> x `seq` x+y)
foldl (+)
วิธีนี้ใช้ไม่ได้ผลและไม่มีอะไรที่สามารถทำได้เพราะไม่มีการแสดงออกใดที่สามารถประเมินตัวเองได้ การประเมินสามารถ "มาจากด้านบน" เท่านั้น seq
ไม่มีความพิเศษ แต่อย่างใดในเรื่องนี้
การลดลงเกิดขึ้นทุกที่
การลด (หรือการประเมินผล) ใน Haskell เกิดขึ้นที่จุดเข้มงวดเท่านั้น [... ] สัญชาตญาณของฉันบอกว่ารูปแบบหลักรูปแบบ seq / bang การจับคู่รูปแบบและการดำเนินการของ IO ใด ๆ ที่ดำเนินการผ่าน main เป็นจุดเข้มงวดหลัก [... ]
ฉันไม่เห็นว่าจะเข้าใจคำพูดนั้นได้อย่างไร ทุกส่วนของโปรแกรมมีความหมายบางอย่างและความหมายนั้นกำหนดโดยกฎการลดดังนั้นการลดจึงเกิดขึ้นทุกที่
ในการลดแอปพลิเคชันฟังก์ชัน<expr1> <expr2>
คุณต้องประเมิน<expr1>
จนกว่าจะมีรูปแบบเหมือน(\x -> <expr1'>)
หรือ(getChar >>=)
หรืออย่างอื่นที่ตรงกับกฎ แต่ด้วยเหตุผลบางประการแอปพลิเคชันฟังก์ชันมักจะไม่ปรากฏในรายการนิพจน์ที่ถูกกล่าวหาว่าเป็น "การประเมินการบังคับ" ในขณะที่case
มักจะทำ
คุณสามารถดูความเข้าใจผิดนี้ได้ในคำพูดจาก Haskell wiki ซึ่งพบในคำตอบอื่น:
ในทางปฏิบัติ Haskell ไม่ใช่ภาษาขี้เกียจอย่างเดียว: การจับคู่รูปแบบเช่นมักจะเข้มงวด
ฉันไม่เข้าใจว่าอะไรสามารถทำให้เป็น "ภาษาขี้เกียจล้วนๆ" สำหรับใครก็ตามที่เขียนเช่นนั้นยกเว้นบางทีภาษาที่ทุกโปรแกรมค้างเนื่องจากรันไทม์ไม่เคยทำอะไรเลย หากการจับคู่รูปแบบเป็นคุณลักษณะของภาษาของคุณคุณต้องดำเนินการในบางจุด ในการทำคุณต้องประเมิน scrutinee ให้เพียงพอว่าตรงกับรูปแบบหรือไม่ นั่นเป็นวิธีที่ขี้เกียจที่สุดในการจับคู่รูปแบบที่เป็นไปได้ตามหลักการ
~
รูปแบบที่กำหนดไว้ล่วงหน้ามักเรียกว่า "ขี้เกียจ" โดยโปรแกรมเมอร์ แต่ข้อกำหนดภาษาเรียกสิ่งเหล่านี้ว่า "หักล้างไม่ได้" คุณสมบัติที่กำหนดของพวกเขาคือพวกเขาตรงกันเสมอ เนื่องจากเสมอกันคุณจึงไม่จำเป็นต้องประเมิน scrutinee เพื่อพิจารณาว่าตรงกันหรือไม่ดังนั้นการใช้งานที่ขี้เกียจจึงไม่เกิดขึ้น ความแตกต่างระหว่างรูปแบบปกติและรูปแบบที่หักล้างไม่ได้คือนิพจน์ที่ตรงกันไม่ใช่กลยุทธ์การประเมินผลที่คุณควรใช้ ข้อมูลจำเพาะไม่ได้กล่าวถึงกลยุทธ์การประเมินผล
main
เป็นจุดเข้มงวด ได้รับการกำหนดเป็นพิเศษให้เป็นจุดเข้มงวดหลักของบริบท: โปรแกรม เมื่อmain
มีการประเมินบริบทของโปรแกรมจุดเข้มงวดของ main จะเปิดใช้งาน [ ... ] หลักมักจะประกอบด้วยการกระทำ IO main
ซึ่งยังจุดที่เข้มงวดซึ่งมีบริบท
ฉันไม่มั่นใจว่าสิ่งนั้นมีความหมายใด ๆ
ความลึกของเมนสูงสุด: ต้องได้รับการประเมินอย่างครบถ้วน
ไม่จำเป็นต้องmain
ได้รับการประเมินแบบ "ตื้น ๆ " เท่านั้นเพื่อให้การดำเนินการ I / O ปรากฏที่ระดับบนสุด main
เป็นโปรแกรมทั้งหมดและโปรแกรมไม่ได้รับการประเมินอย่างสมบูรณ์ในทุกการรันเนื่องจากโค้ดทั้งหมดไม่เกี่ยวข้องกับการรันทุกครั้ง (โดยทั่วไป)
พูดคุยseq
และจับคู่รูปแบบในข้อกำหนดเหล่านี้
ฉันได้พูดคุยเกี่ยวกับการจับคู่รูปแบบแล้ว seq
สามารถกำหนดได้โดยกฎที่มีความคล้ายคลึงกับcase
และการประยุกต์ใช้: ยกตัวอย่างเช่นลด(\x -> <expr1>) `seq` <expr2>
<expr2>
"บังคับให้ประเมิน" นี้ในลักษณะเดียวกับที่case
แอปพลิเคชันทำ WHNF เป็นเพียงชื่อของนิพจน์เหล่านี้ "การประเมินผลบังคับ" เป็น
อธิบายความแตกต่างของแอพพลิเคชั่นฟังก์ชัน: มันเข้มงวดแค่ไหน? ไม่เป็นได้อย่างไร
มันเข้มงวดในการแสดงออกทางซ้ายเช่นเดียวกับที่case
เข้มงวดในการตรวจสอบข้อเท็จจริง นอกจากนี้ยังเข้มงวดในส่วนของฟังก์ชันหลังจากการเปลี่ยนตัวเช่นเดียวกับที่case
เข้มงวดใน RHS ของทางเลือกที่เลือกหลังจากการเปลี่ยนตัว
เกี่ยวกับอะไรdeepseq
?
เป็นเพียงฟังก์ชันไลบรารีไม่ใช่ในตัว
บังเอิญdeepseq
เป็นเรื่องแปลกทางความหมาย ควรใช้เวลาเพียงหนึ่งข้อโต้แย้ง ฉันคิดว่าใครก็ตามที่คิดค้นมันขึ้นมาก็แค่ลอกเลียนแบบสุ่มสี่สุ่มห้าseq
โดยไม่เข้าใจว่าทำไมseq
ต้องมีสองข้อโต้แย้ง ฉันนับdeepseq
ชื่อและคุณสมบัติเป็นหลักฐานว่าความเข้าใจที่ไม่ดีเกี่ยวกับการประเมินผลของ Haskell นั้นเป็นเรื่องปกติแม้แต่ในหมู่โปรแกรมเมอร์ที่มีประสบการณ์ของ Haskell
let
และcase
งบ?
case
ผมได้พูดคุยเกี่ยวกับ let
หลังจาก desugaring และตรวจสอบประเภทเป็นเพียงวิธีการเขียนกราฟนิพจน์โดยพลการในรูปแบบต้นไม้ นี่คือกระดาษเกี่ยวกับเรื่องนี้
unsafePerformIO
เหรอ?
ในขอบเขตที่สามารถกำหนดได้โดยกฎการลด ยกตัวอย่างเช่นการcase unsafePerformIO <expr> of <alts>
ลดunsafePerformIO (<expr> >>= \x -> return (case x of <alts>))
และในระดับบนสุดเท่านั้นลดไปunsafePerformIO <expr>
<expr>
นี่ไม่ได้ทำบันทึกใด ๆ คุณสามารถลองจำลองการบันทึกโดยการเขียนใหม่ทุกunsafePerformIO
นิพจน์เพื่อบันทึกตัวเองอย่างชัดเจนและสร้างIORef
s ... แต่คุณไม่สามารถทำซ้ำพฤติกรรมการบันทึกของ GHC ได้เนื่องจากขึ้นอยู่กับรายละเอียดที่ไม่สามารถคาดเดาได้ของกระบวนการเพิ่มประสิทธิภาพและเนื่องจากมันไม่ได้เป็นประเภทที่ปลอดภัย (ดังที่แสดงในIORef
ตัวอย่างโพลีมอร์ฟิกที่น่าอับอายในเอกสาร GHC)
Debug.Trace
เหรอ?
Debug.Trace.trace
เป็นเพียงกระดาษห่อหุ้มธรรมดา ๆ unsafePerformIO
เป็นเพียงเสื้อคลุมรอบที่เรียบง่าย
คำจำกัดความระดับสูงสุด?
การเชื่อมโยงตัวแปรระดับบนสุดจะเหมือนกับlet
การเชื่อมโยงแบบซ้อนกัน data
และclass
นั่นimport
คือเกมบอลที่แตกต่างกันโดยสิ้นเชิง
ประเภทข้อมูลที่เข้มงวด? รูปแบบปัง?
เพียงน้ำตาลสำหรับseq
.
seq
และการจับคู่รูปแบบนั้นเพียงพอแล้วโดยส่วนที่เหลือกำหนดไว้ในแง่ของสิ่งเหล่านั้น ฉันคิดว่าการจับคู่รูปแบบทำให้มั่นใจได้ถึงความเข้มงวดของIO
การกระทำตัวอย่างเช่น