ความเข้มงวดของ Haskell คืออะไร?


90

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

ลด (หรือการประเมินผล) ใน Haskell เพียงเกิดขึ้นในจุดที่เข้มงวด

ดังนั้นคำถามคือสิ่งที่แม่นยำเป็น Haskell จุดเข้มงวด? สัญชาตญาณของฉันบอกว่าmain, seq/ รูปแบบปังจับคู่รูปแบบและใด ๆIOการดำเนินการดำเนินการผ่านทางmainเป็นประเด็นหลักเข้มงวด แต่ผมไม่ทราบจริงๆว่าทำไมฉันรู้ว่า

(ถ้าไม่เรียกว่า "จุดเข้มงวด" จะเรียกว่าอะไร)

ฉันคิดว่าคำตอบที่ดีจะรวมถึงการอภิปรายเกี่ยวกับ WHNF และอื่น ๆ ฉันยังคิดว่ามันอาจสัมผัสกับแคลคูลัสแลมบ์ดา


แก้ไข: ความคิดเพิ่มเติมเกี่ยวกับคำถามนี้

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

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

ตอนนี้คุณลอง: พูดคุยseqและจับคู่รูปแบบในเงื่อนไขเหล่านี้ อธิบายความแตกต่างของแอพพลิเคชั่นฟังก์ชั่น: มันเข้มงวดแค่ไหน? ไม่เป็นได้อย่างไร เกี่ยวกับอะไรdeepseq? letและcaseงบ? unsafePerformIOเหรอ? Debug.Traceเหรอ? คำจำกัดความระดับสูงสุด? ประเภทข้อมูลที่เข้มงวด? รูปแบบปัง? เป็นต้นมีกี่รายการที่สามารถอธิบายได้ในแง่ของการจับคู่ seq หรือรูปแบบ?


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

Primitives เช่น+ในประเภทตัวเลขในตัวก็บังคับใช้ความเข้มงวดเช่นกันและฉันคิดว่าสิ่งเดียวกันนี้ใช้กับการเรียก FFI แท้
hammar

4
ดูเหมือนจะมีสองแนวคิดที่สับสนที่นี่ การจับคู่รูปแบบและรูปแบบ seq และ bang เป็นวิธีที่นิพจน์สามารถเข้มงวดในนิพจน์ย่อยนั่นคือถ้านิพจน์ด้านบนได้รับการประเมินนิพจน์ย่อยก็เช่นกัน ในทางตรงกันข้ามหลักดำเนินการ IO เป็นวิธีการประเมินผลการเริ่มต้น สิ่งเหล่านี้เป็นสิ่งที่แตกต่างกันและเป็นข้อผิดพลาดประเภทที่จะรวมไว้ในรายการเดียวกัน
Chris Smith

@ChrisSmith ฉันไม่ได้พยายามที่จะสับสนทั้งสองกรณีที่แตกต่างกัน หากมีสิ่งใดที่ฉันขอให้ชี้แจงเพิ่มเติมเกี่ยวกับวิธีที่พวกเขาโต้ตอบ ความเข้มงวดเกิดขึ้นอย่างใดอย่างหนึ่งและทั้งสองกรณีมีความสำคัญแม้ว่าจะแตกต่างกันบางส่วนของความเข้มงวด "เกิดขึ้น" (และ @ monadic: ಠ_ಠ)
Dan Burton

หากคุณต้องการ / ต้องการห้องเพื่อหารือเกี่ยวกับประเด็นต่างๆของคำถามนี้โดยไม่ต้องพยายามตอบคำถามทั้งหมดให้ฉันแนะนำให้ใช้ความคิดเห็นในโพสต์ / r / haskell ของฉันสำหรับคำถามนี้
Dan Burton

คำตอบ:


46

จุดเริ่มต้นที่ดีคือการทำความเข้าใจบทความนี้: A Natural Semantics for Lazy Evalution (Launchbury) ซึ่งจะบอกคุณเมื่อมีการประเมินนิพจน์สำหรับภาษาขนาดเล็กที่คล้ายกับ Core ของ GHC จากนั้นคำถามที่เหลือคือวิธีการแมป Haskell เต็มรูปแบบกับ Core และการแปลส่วนใหญ่ได้รับจากรายงาน Haskell เอง ใน GHC เราเรียกกระบวนการนี้ว่า "desugaring" เนื่องจากจะกำจัดน้ำตาลที่เป็นประโยค

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

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


18
ฉันพบว่ามันน่าขบขันเสมอในสิ่งที่ GHC เรียกว่า "desugaring" น้ำตาลวากยสัมพันธ์ที่ถูกลบออกนั้นรวมถึงไวยากรณ์ที่แท้จริงของภาษา Haskell นั้นเอง ... โดยนัยว่า GHC เป็นคอมไพเลอร์ที่ปรับให้เหมาะสมสำหรับ GHC ภาษาหลักซึ่งบังเอิญรวมถึงส่วนหน้าที่ซับซ้อนมากสำหรับการแปล Haskell เป็น Core :]
CA McCann

แม้ว่าระบบประเภทจะไม่ได้ใช้งานอย่างแม่นยำ ... และสิ่งล่าสุดของ TF / GADT อย่างที่ฉันเข้าใจทำให้ช่องว่างนั้นกว้างขึ้น
sclv

1
GCC ไม่ได้เพิ่มประสิทธิภาพ C เช่นกัน: gcc.gnu.org/onlinedocs/gccint/Passes.html#Passes
György Andrasek

20

ฉันอาจจะตั้งคำถามใหม่ว่า Haskell จะประเมินนิพจน์ภายใต้สถานการณ์ใด (บางทีอาจจะใช้ "หัวอ่อนแบบปกติ")

ในการประมาณครั้งแรกเราสามารถระบุสิ่งนี้ได้ดังนี้:

  • การดำเนินการกับ IO จะประเมินนิพจน์ใด ๆ ที่พวกเขา“ ต้องการ” (ดังนั้นคุณต้องทราบว่ามีการดำเนินการ IO หรือไม่เช่นชื่อเป็น main หรือเรียกจาก main และคุณต้องรู้ว่าการดำเนินการนั้นต้องการอะไร)
  • นิพจน์ที่กำลังถูกประเมิน (เฮ้นั่นคือนิยามแบบวนซ้ำ!) จะประเมินนิพจน์ใด ๆ ที่ต้องการ

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

การให้รายละเอียดทั้งหมดโดยเฉพาะเป็นงานที่มีขนาดใหญ่เนื่องจาก Haskell เป็นภาษาขนาดใหญ่ นอกจากนี้ยังค่อนข้างละเอียดอ่อนเนื่องจาก Concurrent Haskell อาจประเมินสิ่งต่าง ๆ อย่างคาดเดาแม้ว่าเราจะไม่ได้ใช้ผลลัพธ์ในตอนท้าย: นี่เป็นสิ่งที่สามที่ทำให้เกิดการประเมิน ประเภทที่สองมีการศึกษาค่อนข้างดี: คุณต้องการดูความเข้มงวดของฟังก์ชันที่เกี่ยวข้อง ประเภทแรกก็สามารถคิดได้ว่าเป็น "ความเข้มงวด" เช่นกันแม้ว่านี่จะเป็นการหลีกเลี่ยงเล็กน้อยเพราะevaluate xและseq x $ return ()เป็นสิ่งที่แตกต่างกันจริงๆ! คุณสามารถปฏิบัติได้อย่างถูกต้องหากคุณให้ความหมายบางอย่างกับ IO monad (การส่งRealWorld#โทเค็นอย่างชัดเจนสำหรับกรณีง่ายๆ) แต่ฉันไม่รู้ว่าโดยทั่วไปมีชื่อสำหรับการวิเคราะห์ความเข้มงวดแบบแบ่งชั้นประเภทนี้หรือไม่


17

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

ในทางปฏิบัติ Haskell ไม่ใช่ภาษาเกียจคร้านอย่างแท้จริงตัวอย่างเช่นการจับคู่รูปแบบมักจะเข้มงวด (ดังนั้นการพยายามประเมินรูปแบบการจับคู่กองกำลังจะเกิดขึ้นอย่างน้อยพอที่จะยอมรับหรือปฏิเสธการจับคู่

โปรแกรมเมอร์ยังสามารถใช้seqพื้นฐานเพื่อบังคับให้นิพจน์ประเมินโดยไม่คำนึงว่าผลลัพธ์จะถูกใช้หรือไม่

$!seqถูกกำหนดไว้ในแง่ของ

- ขี้เกียจกับที่ไม่เข้มงวด

ดังนั้นความคิดของคุณเกี่ยวกับ!/ $!และseqเป็นสิ่งที่ถูกต้อง แต่การจับคู่รูปแบบจะขึ้นอยู่กับกฎที่ละเอียดกว่า แน่นอนคุณสามารถใช้~เพื่อบังคับให้จับคู่รูปแบบขี้เกียจได้ตลอดเวลา ประเด็นที่น่าสนใจจากบทความเดียวกัน:

นอกจากนี้ตัววิเคราะห์ความเข้มงวดยังมองหากรณีที่นิพจน์ย่อยจำเป็นต้องใช้เสมอโดยนิพจน์ภายนอกและแปลงนิพจน์เหล่านั้นเป็นการประเมินอย่างกระตือรือร้น สามารถทำได้เนื่องจากความหมาย (ในแง่ของ "ด้านล่าง") ไม่เปลี่ยนแปลง

ไปที่โพรงกระต่ายต่อไปและดูเอกสารสำหรับการเพิ่มประสิทธิภาพที่ดำเนินการโดย GHC:

การวิเคราะห์ความเข้มงวดเป็นกระบวนการที่ GHC พยายามกำหนดในเวลาคอมไพล์ซึ่งข้อมูลใดที่ 'จำเป็นเสมอ' จากนั้น GHC สามารถสร้างโค้ดเพื่อคำนวณข้อมูลดังกล่าวแทนที่จะเป็นกระบวนการปกติ (ค่าใช้จ่ายที่สูงกว่า) เพื่อจัดเก็บการคำนวณและดำเนินการในภายหลัง

- optimisations GHC: เข้มงวดการวิเคราะห์

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

…ไม่สามารถทำการประเมินมูลค่าได้อีกต่อไป มันก็บอกว่าจะอยู่ในรูปแบบปกติ หากเราอยู่ในขั้นตอนกลางขั้นตอนใดขั้นตอนหนึ่งเพื่อให้เราทำการประเมินค่าอย่างน้อยที่สุดค่านั้นจะอยู่ในรูปแบบปกติของส่วนหัวที่อ่อนแอ (WHNF) (นอกจากนี้ยังมี 'head normal form' แต่ไม่ได้ใช้ใน Haskell) การประเมินบางสิ่งใน WHNF โดยสมบูรณ์จะลดลงเป็นบางอย่างในรูปแบบปกติ ...

- Wikibooks Haskell: ความเกียจคร้าน

(คำที่อยู่ในรูปแบบปกติของส่วนหัวถ้าไม่มี beta-Redx ในตำแหน่งหัว1 เรดเอ็กซ์คือส่วนหัวเรดเอ็กซ์หากนำหน้าด้วยแลมบ์ดานามธรรมที่ไม่ใช่เรดเอ็กซ์2เท่านั้น) ดังนั้นเมื่อคุณเริ่มบังคับลดขนาด คุณทำงานใน WHNF; เมื่อไม่มีสิ่งใดเหลือให้บังคับคุณก็อยู่ในรูปแบบปกติ อีกประเด็นที่น่าสนใจ:

…ถ้าถึงจุดหนึ่งเราจำเป็นต้องพูดพิมพ์ z ให้ผู้ใช้เราต้องประเมินอย่างละเอียด…

ที่เป็นธรรมชาติแสดงให้เห็นว่าจริง ๆIOการดำเนินการจากการmain ไม่ประเมินผลการบังคับใช้ซึ่งควรจะชัดเจนพิจารณาว่าโปรแกรม Haskell ไม่ในความเป็นจริงทำสิ่ง สิ่งที่ต้องดำเนินไปตามลำดับที่กำหนดไว้mainจะต้องอยู่ในรูปแบบปกติดังนั้นจึงต้องได้รับการประเมินอย่างเข้มงวด

CA McCann เข้าใจถูกต้องในความคิดเห็นแม้ว่าสิ่งเดียวที่พิเศษเกี่ยวกับเรื่องmainนี้mainคือถูกกำหนดให้เป็นพิเศษ การจับคู่รูปแบบบนตัวสร้างนั้นเพียงพอที่จะทำให้แน่ใจว่าลำดับที่กำหนดโดยIOmonad ในแง่นั้นเท่านั้นseqและการจับคู่รูปแบบเป็นพื้นฐาน


4
จริงๆแล้วคำพูด "ถ้าในบางจุดเราจำเป็นต้องพิมพ์ z ออกไปให้ผู้ใช้เราจำเป็นต้องประเมินทั้งหมด" นั้นไม่ถูกต้องทั้งหมด มีความเข้มงวดเช่นเดียวกับShowอินสแตนซ์สำหรับค่าที่กำลังพิมพ์
nominolo

10

Haskell เป็นภาษา AFAIK ไม่ใช่ภาษาขี้เกียจ แต่เป็นภาษาที่ไม่เข้มงวด ซึ่งหมายความว่าไม่จำเป็นต้องประเมินเงื่อนไขในช่วงเวลาสุดท้ายที่เป็นไปได้

แหล่งข้อมูลที่ดีสำหรับโมเดล "ความเกียจคร้าน" ของ haskell สามารถพบได้ที่นี่: http://en.wikibooks.org/wiki/Haskell/Laziness

โดยทั่วไปสิ่งสำคัญคือต้องเข้าใจความแตกต่างระหว่าง thunk และส่วนหัวที่อ่อนแอรูปแบบปกติ WHNF

ความเข้าใจของฉันคือ haskell ดึงการคำนวณไปข้างหลังเมื่อเทียบกับภาษาที่จำเป็น สิ่งนี้หมายความว่าหากไม่มีรูปแบบ "seq" และปังในที่สุดก็จะเป็นผลข้างเคียงบางอย่างที่บังคับให้มีการประเมินผลของ thunk ซึ่งอาจทำให้เกิดการประเมินล่วงหน้า (ความขี้เกียจที่แท้จริง)

เนื่องจากจะนำไปสู่การรั่วไหลของพื้นที่ที่น่ากลัวคอมไพเลอร์จึงหาวิธีและเวลาที่จะประเมิน thunks ล่วงหน้าเพื่อประหยัดพื้นที่ จากนั้นโปรแกรมเมอร์สามารถสนับสนุนกระบวนการนี้ได้โดยให้คำอธิบายประกอบที่เข้มงวด (en.wikibooks.org/wiki/Haskell/Strictness, www.haskell.org/haskellwiki/Performance/Strictness) เพื่อลดการใช้พื้นที่เพิ่มเติมในรูปแบบของ thunks ที่ซ้อนกัน

ฉันไม่ใช่ผู้เชี่ยวชาญด้านความหมายเชิงปฏิบัติการของ haskell ดังนั้นฉันจะทิ้งลิงค์ไว้เป็นแหล่งข้อมูล

แหล่งข้อมูลเพิ่มเติม:

http://www.haskell.org/haskellwiki/Performance/Laziness

http://www.haskell.org/haskellwiki/Haskell/Lazy_Evaluation


6

ขี้เกียจไม่ได้หมายความว่าไม่ทำอะไรเลย เมื่อใดก็ตามที่รูปแบบโปรแกรมของคุณตรงกับcaseนิพจน์โปรแกรมจะประเมินบางสิ่ง - ก็เพียงพอแล้ว ไม่งั้นจะคิดไม่ออกว่าจะใช้ RHS ตัวไหน ไม่เห็นนิพจน์กรณีใด ๆ ในรหัสของคุณ? ไม่ต้องกังวลคอมไพเลอร์กำลังแปลโค้ดของคุณเป็นรูปแบบ Haskell ที่ถอดออกซึ่งยากที่จะหลีกเลี่ยงการใช้งาน

สำหรับผู้เริ่มต้นกฎพื้นฐานletคือขี้เกียจขี้เกียจcaseน้อยกว่า


2
โปรดทราบว่าแม้ว่าcaseจะบังคับใช้การประเมินใน GHC Core เสมอ แต่ก็ไม่ได้ทำเช่นนี้ใน Haskell ปกติ case undefined of _ -> 42ตัวอย่างเช่นลอง
hammar

2
caseใน GHC Core ประเมินอาร์กิวเมนต์เป็น WHNF ในขณะที่caseใน Haskell ประเมินอาร์กิวเมนต์เท่าที่จำเป็นเพื่อเลือกสาขาที่เหมาะสม ในตัวอย่างของฮัมมาร์นั่นไม่ใช่เลย แต่ในcase 1:undefined of x:y:z -> 42นั้นประเมินได้ลึกกว่า WHNF
สูงสุด

และยัง case something of (y,x) -> (x,y)ไม่จำเป็นต้องประเมินsomethingเลย ซึ่งเป็นจริงสำหรับผลิตภัณฑ์ทุกประเภท
Ingo

@Ingo - ไม่ถูกต้อง somethingจะต้องได้รับการประเมินเป็น WHNF เพื่อเข้าถึงตัวสร้างทูเปิล
John L

จอห์น - ทำไม? เรารู้ว่ามันต้องเป็นทูเปิลดังนั้นจุดที่จะประเมินมันอยู่ที่ไหน? พอเพียงถ้า x และ y ถูกผูกไว้กับโค้ดที่ประเมินทูเพิลและแยกช่องที่เหมาะสมหากจำเป็นต้องใช้ตัวเอง
Ingo

4

นี่ไม่ใช่คำตอบที่สมบูรณ์ที่มุ่งเป้าไปที่กรรม แต่เป็นเพียงส่วนหนึ่งของปริศนา - ในระดับที่เกี่ยวกับความหมายโปรดจำไว้ว่ามีกลยุทธ์การประเมินผลหลายแบบที่ให้ความหมายเดียวกัน ตัวอย่างที่ดีอย่างหนึ่งที่นี่ - และโครงการยังพูดถึงวิธีที่เราคิดเกี่ยวกับความหมายของ Haskell โดยทั่วไปคือโครงการ Eager Haskell ซึ่งเปลี่ยนแปลงกลยุทธ์การประเมินผลอย่างรุนแรงในขณะที่ยังคงความหมายเดิมไว้: http://csg.csail.mit.edu/ pubs / haskell.html


2

คอมไพเลอร์กลาสโกว์ Haskell แปลรหัสของคุณเป็นภาษาที่เรียกว่าแลมบ์ดาแคลคูลัสเหมือนหลัก ในภาษานี้จะมีการประเมินบางสิ่งบางอย่างเมื่อใดก็ตามที่คุณจับคู่รูปแบบโดยใช้case-statement ดังนั้นหากมีการเรียกใช้ฟังก์ชันตัวสร้างด้านนอกสุดและเฉพาะฟังก์ชันนั้น (ถ้าไม่มีฟิลด์บังคับ) จะได้รับการประเมิน อย่างอื่นบรรจุกระป๋องเป็นก้อน (Thunks ถูกนำมาใช้โดยletการผูก)

แน่นอนว่านี่ไม่ใช่สิ่งที่เกิดขึ้นในภาษาจริง คอมไพเลอร์จะแปลง Haskell เป็น Core ด้วยวิธีที่ซับซ้อนมากทำให้หลาย ๆ อย่างเป็นไปได้ว่าขี้เกียจและอะไรก็ตามที่จำเป็นเสมอขี้เกียจ นอกจากนี้ยังมีค่า unboxed และ tuples ที่เข้มงวดเสมอ

หากคุณพยายามประเมินฟังก์ชันด้วยมือโดยพื้นฐานแล้วคุณสามารถคิดว่า:

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

0

เราทุกคนรู้ (หรือควรรู้) ว่า 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นิพจน์เพื่อบันทึกตัวเองอย่างชัดเจนและสร้างIORefs ... แต่คุณไม่สามารถทำซ้ำพฤติกรรมการบันทึกของ GHC ได้เนื่องจากขึ้นอยู่กับรายละเอียดที่ไม่สามารถคาดเดาได้ของกระบวนการเพิ่มประสิทธิภาพและเนื่องจากมันไม่ได้เป็นประเภทที่ปลอดภัย (ดังที่แสดงในIORefตัวอย่างโพลีมอร์ฟิกที่น่าอับอายในเอกสาร GHC)

Debug.Traceเหรอ?

Debug.Trace.trace เป็นเพียงกระดาษห่อหุ้มธรรมดา ๆ unsafePerformIOเป็นเพียงเสื้อคลุมรอบที่เรียบง่าย

คำจำกัดความระดับสูงสุด?

การเชื่อมโยงตัวแปรระดับบนสุดจะเหมือนกับletการเชื่อมโยงแบบซ้อนกัน dataและclassนั่นimportคือเกมบอลที่แตกต่างกันโดยสิ้นเชิง

ประเภทข้อมูลที่เข้มงวด? รูปแบบปัง?

เพียงน้ำตาลสำหรับseq.

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.