มีกฎอะไรบ้างเกี่ยวกับฟังก์ชัน a -> () ที่ถูกประเมินใน Haskell


12

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

f :: a -> ()
f _ = undefined

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

g :: [a] -> ()
g [] = ()
g (_:xs) = g xs

รหัสนี้จะวนg (let x = 1:x in x)ซ้ำไปเรื่อย ๆ หากแสดงด้วย แต่แล้ว

f :: a -> ()
f _ = undefined
h :: a -> ()
h _ = ()

สามารถใช้เพื่อแสดงh (f 1)ผลตอบแทน()ได้ดังนั้นในกรณีนี้ไม่ได้ประเมิน subexpressions ที่มีค่าหน่วยทั้งหมด กฎทั่วไปที่นี่คืออะไร?

การทางพิเศษแห่งประเทศไทย: แน่นอนฉันรู้เกี่ยวกับความเกียจคร้าน ฉันถามสิ่งที่ป้องกันไม่ให้นักเขียนคอมไพเลอร์ทำให้กรณีนี้แม้แต่ขี้เกียจกว่าปกติ

ETA2: สรุปตัวอย่าง: GHC ดูเหมือนจะปฏิบัติ()เหมือนกับประเภทอื่น ๆ ทุกประการเช่นราวกับว่ามีคำถามเกี่ยวกับค่าปกติที่พำนักอยู่ควรส่งคืนประเภทจากฟังก์ชัน ความจริงที่ว่ามีเพียงหนึ่งค่าดังกล่าวดูเหมือนจะไม่ (ab) ใช้โดยอัลกอริทึมการเพิ่มประสิทธิภาพ

ETA3: เมื่อฉันพูด Haskell ฉันหมายถึง Haskell ตามที่กำหนดโดยรายงานไม่ใช่ Haskell-the-H-in-GHC ดูเหมือนว่าจะเป็นสมมติฐานที่ไม่ได้แชร์กันอย่างกว้างขวางเท่าที่ฉันจินตนาการ (ซึ่งเป็น '100% ของผู้อ่าน') หรือฉันอาจจะสามารถตั้งคำถามที่ชัดเจนขึ้นได้ ถึงกระนั้นฉันก็เสียใจที่เปลี่ยนชื่อของคำถามเพราะเดิมทีมันถามว่ามีการรับประกันอะไรสำหรับฟังก์ชั่นดังกล่าว

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

ฉันพบปัญหาจริงเมื่อทำการย้ายแอป OCaml ไปยัง Haskell แอปดั้งเดิมมีโครงสร้างแบบเรียกซ้ำหลายชนิดและรหัสประกาศฟังก์ชันที่เรียกว่าassert_structureN_is_correctN ใน 1..6 หรือ 7 ซึ่งแต่ละหน่วยส่งคืนหากโครงสร้างถูกต้องแน่นอนและโยนข้อยกเว้นถ้าไม่ใช่ . นอกจากนี้ฟังก์ชั่นเหล่านี้เรียกว่ากันและกันเมื่อพวกเขาย่อยสลายเงื่อนไขความถูกต้อง ใน Haskell สิ่งนี้จัดการได้ดีกว่าการใช้Either Stringmonad ดังนั้นฉันจึงถอดความมันแบบนั้น แต่คำถามที่เป็นประเด็นทางทฤษฎียังคงอยู่ ขอบคุณสำหรับอินพุตและคำตอบทั้งหมด


1
นี่คือความเกียจคร้านในที่ทำงาน หากไม่ได้รับผลลัพธ์ของฟังก์ชัน (เช่นโดยการจับคู่รูปแบบกับตัวสร้าง) เนื้อความของฟังก์ชันจะไม่ถูกประเมิน จะสังเกตเห็นความแตกต่างให้ลองเปรียบเทียบและh1::()->() ; h1 () = () h2::()->() ; h2 _ = ()เรียกใช้ทั้งสองh1 (f 1)และh2 (f 1)และดูว่ามีเพียงความต้องการแรก(f 1)เท่านั้น
Chi

1
"ความเกียจคร้านดูเหมือนจะบอกว่ามันถูกแทนที่ด้วย () โดยไม่มีการประเมินผลใด ๆ เกิดขึ้น" นั่นหมายความว่าอย่างไร? f 1คือ "แทนที่" โดยundefinedในทุกกรณี
oisdk

3
ฟังก์ชั่น... -> ()สามารถ 1) ยุติและส่งคืน()2) ยุติด้วยข้อผิดพลาดข้อยกเว้น / runtime และล้มเหลวที่จะกลับอะไรหรือ 3) diverge (เรียกซ้ำไม่สิ้นสุด) GHC ไม่ได้เพิ่มประสิทธิภาพของรหัสสมมติเพียง 1) สามารถเกิดขึ้นได้ถ้ามีการเรียกร้องก็ไม่ได้ข้ามการประเมินผลและการกลับมาของf 1 ()ความหมายของ Haskell คือการประเมินและดูว่าเกิดอะไรขึ้นกับ 1,2,3
Chi

2
ไม่มีอะไรพิเศษจริงๆเกี่ยวกับ()(ประเภทหรือค่า) ในคำถามนี้ ข้อสังเกตเดียวกันทั้งหมดเกิดขึ้นหากคุณแทนที่() :: ()พูด0 :: Intทุกที่ สิ่งเหล่านี้ล้วน แต่น่าเบื่อที่จะตามมาจากการประเมินที่ขี้เกียจ
Daniel Wagner

2
ไม่ "การหลีกเลี่ยง" ฯลฯ ไม่ใช่ความหมายของ Haskell และมีสองค่าที่เป็นไปของ()ชนิดและ() undefined
Will Ness

คำตอบ:


10

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

นี่ไม่ใช่วิธีการทำงานของ Haskell ประเภททุกคนมีหนึ่งค่าอื่น ๆ ใน Haskell คือไม่มีค่าผิดพลาดหรือที่เรียกว่า "ด้านล่าง" undefinedเข้ารหัสโดย ดังนั้นการประเมินผลจึงเกิดขึ้นจริง:

main = print (f 1)

เทียบเท่ากับภาษาคอร์

main = _Case (f 1) _Of x -> print x   -- pseudocode illustration

หรือแม้กระทั่ง(*)

main = _Case (f 1) _Of x -> putStr "()"

และ Core ของ_Caseถูกบังคับให้ :

"การประเมิน%case[นิพจน์] บังคับให้การประเมินผลของนิพจน์ที่กำลังทดสอบ (" scrutinee ") ค่าของ scrutinee ถูกผูกไว้กับตัวแปรตาม%ofคำหลัก, ... "

ค่าถูกบังคับให้รูปแบบปกติของหัวอ่อน นี่เป็นส่วนหนึ่งของการกำหนดภาษา

Haskell คือไม่เปิดเผยการเขียนโปรแกรมภาษา


(*) print x = putStr (show x)และshow () = "()"เพื่อให้showสามารถรวบรวมการโทรได้ทั้งหมด

ค่าที่เป็นที่รู้จักกันแน่นอนล่วงหน้า()และแม้กระทั่งค่าของการเป็นที่รู้จักกันล่วงหน้าshow () "()"ยังคงเป็นความหมายที่ได้รับการยอมรับของ Haskell เรียกร้องให้ค่าของ(f 1)ถูกบังคับให้รูปแบบปกติที่อ่อนแอก่อนที่จะดำเนินการพิมพ์ที่รู้จักกันในสตริงล่วงหน้า, "()".


แก้ไข:concat (repeat [])พิจารณา มันควรจะเป็น[]หรือควรจะเป็นวงที่ไม่มีที่สิ้นสุด?

"ภาษาบอกเล่า" []ของคำตอบนี้เป็นส่วนใหญ่อาจ คำตอบของ Haskell เป็นห่วงไม่มีที่สิ้นสุด

ความเกียจคร้านเป็น "คนจนเขียนโปรแกรมเปิดเผย" แต่ก็ยังไม่ได้เป็นสิ่งที่จริง

แก้ไข 2 : print $ h (f 1) == _Case (h (f 1)) _Of () -> print ()และhถูกบังคับเท่านั้นไม่ใช่f; และการผลิตของคำตอบที่ไม่ได้มีการบังคับให้อะไรตามคำนิยามของhh _ = ()

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

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


ไปตามรายงานเรามี

f :: a -> ()
f = \_ -> (undefined :: ())

แล้วก็

print (f 1)
 = print ((\ _ ->  undefined :: ()) 1)
 = print          (undefined :: ())
 = putStrLn (show (undefined :: ()))

และด้วย

instance Show () where
    show :: () -> String
    show x = case x of () -> "()"

มันยังคง

 = putStrLn (case (undefined :: ()) of () -> "()")

ตอนนี้ส่วน3.17.3 ความหมายแบบทางการของการจับคู่รูปแบบของรายงานกล่าวว่า

ความหมายของcaseการแสดงออก [ได้รับ] ในรูปที่ 3.1–3.3 การใช้งานใด ๆ ควรมีพฤติกรรมเพื่อให้ตัวตนเหล่านี้มี [... ]

และเคส(r)ในรูปที่ 3.2สถานะ

(r)     case  of { K x1  xn -> e; _ -> e } =  
        where K is a data constructor of arity n 

() เป็นตัวสร้างข้อมูลของ arity 0 ดังนั้นมันจึงเหมือนกับ

(r)     case  of { () -> e; _ -> e } =  

และผลลัพธ์โดยรวมของการประเมินจึงเป็นเช่นนั้น


2
ฉันชอบคำอธิบายของคุณ มันชัดเจนและเรียบง่าย
arrowd

@DanielWagner ฉันคิดในใจcaseจาก Core จริงและไม่สนใจช่องโหว่ :) ฉันได้แก้ไขพูดถึง Core แล้ว
Will Ness

1
การบังคับใช้จะไม่ถูกshowเรียกใช้โดยprintใช่หรือไม่ มีบางอย่างที่เหมือนกันshow x = case x of () -> "()"
user253751

1
ฉันอ้างถึงcaseใน Core ไม่ใช่ใน Haskell Haskell ถูกแปลเป็น Core ซึ่งมีการบังคับcaseAFAIK คุณถูกต้องว่าcaseใน Haskell ไม่ได้บังคับตัวเอง ฉันสามารถเขียนอะไรบางอย่างใน Scheme หรือ ML (ถ้าฉันสามารถเขียน ML นั่นคือ) หรือ pseudocode
Will Ness

1
คำตอบที่มีสิทธิ์ทั้งหมดนี้น่าจะอยู่ที่ไหนสักแห่งในรายงาน สิ่งที่ฉันรู้คือไม่มี "การเพิ่มประสิทธิภาพ" เกิดขึ้นที่นี่และ "ค่าปกติ" ไม่ใช่คำที่มีความหมายในบริบทนี้ อะไรก็ตามที่ถูกบังคับให้ถูกบังคับ printบังคับให้พิมพ์เท่าที่จำเป็น มันไม่ได้ดูที่ประเภทประเภทที่หายไปลบตามเวลาที่โปรแกรมรันรูทีนย่อยการพิมพ์ที่ถูกต้องจะถูกเลือกและเรียบเรียงแล้วตามประเภทที่เวลารวบรวม รูทีนย่อยนั้นจะยังคงบังคับค่าอินพุตให้กับ WHNF ณ รันไทม์และหากไม่ได้กำหนดไว้จะทำให้เกิดข้อผิดพลาด
Will Ness
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.