เครื่องหมายอัศเจรีย์หมายถึงอะไรในการประกาศของ Haskell


261

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

data MidiMessage = MidiMessage !Int !MidiMessage

13
ฉันสงสัยว่านี่อาจเป็นคำถามที่พบบ่อยมาก ฉันจำได้ชัดเจนว่าสงสัยเกี่ยวกับสิ่งเดียวกันนี้เองย้อนกลับไปเมื่อไหร่
cjs

คำตอบ:


313

มันเป็นการประกาศความเข้มงวด โดยทั่วไปหมายความว่าจะต้องประเมินสิ่งที่เรียกว่า "รูปแบบปกติที่อ่อนแอของหัว" เมื่อค่าโครงสร้างข้อมูลถูกสร้างขึ้น ลองดูตัวอย่างเพื่อที่เราจะได้เห็นความหมายของสิ่งนี้:

data Foo = Foo Int Int !Int !(Maybe Int)

f = Foo (2+2) (3+3) (4+4) (Just (5+5))

ฟังก์ชั่นfด้านบนเมื่อประเมินแล้วจะคืนค่า "thunk": นั่นคือรหัสที่เรียกใช้งานเพื่อหาค่าของมัน ณ จุดนี้ฟูยังไม่มีแม้แต่เพียงแค่รหัส

แต่เมื่อถึงจุดหนึ่งบางคนอาจลองมองเข้าไปข้างในอาจผ่านการจับคู่รูปแบบ:

case f of
     Foo 0 _ _ _ -> "first arg is zero"
     _           -> "first arge is something else"

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

ข้อที่สองไม่จำเป็นต้องประเมินเพราะเราไม่ได้ทำการทดสอบ ดังนั้นแทนที่จะถูกเก็บไว้ในสถานที่ตั้งของหน่วยความจำที่เราจะเก็บเพียงรหัสสำหรับการประเมินผลภายหลังเป็นไปได้6 (3+3)ที่จะกลายเป็น 6 เท่านั้นถ้ามีคนมองไปที่มัน

อย่างไรก็ตามพารามิเตอร์ที่สามมี!ส่วนหน้าของมันดังนั้นจะถูกประเมินอย่างเข้มงวด: (4+4)ถูกเรียกใช้และ8ถูกเก็บไว้ในตำแหน่งหน่วยความจำนั้น

พารามิเตอร์ที่สี่ยังถูกประเมินอย่างเคร่งครัด แต่นี่คือจุดที่มันค่อนข้างยุ่งยาก: เรากำลังประเมินไม่เต็มที่ แต่จะอยู่ในรูปหัวปกติที่อ่อนแอเท่านั้น นี่หมายความว่าเรารู้ว่ามันเป็นอะไรNothingหรือJustเปล่าแล้วเก็บมันไว้ แต่เราไม่ไปไกลกว่านี้ นั่นหมายความว่าเราไม่ได้เก็บJust 10แต่จริง ๆ แล้วJust (5+5)ปล่อยให้อันธพาลข้างในไม่ได้ประเมินค่า สิ่งนี้เป็นสิ่งสำคัญที่ต้องรู้แม้ว่าฉันคิดว่าความหมายทั้งหมดของสิ่งนี้จะไปไกลเกินขอบเขตของคำถามนี้

คุณสามารถใส่คำอธิบายประกอบอาร์กิวเมนต์ของฟังก์ชั่นในลักษณะเดียวกันหากคุณเปิดใช้งานBangPatternsส่วนขยายภาษา:

f x !y = x*y

f (1+1) (2+2)จะกลับ (1+1)*4thunk


16
สิ่งนี้มีประโยชน์มาก ฉันอดไม่ได้ที่จะสงสัยว่าคำศัพท์ของ Haskell ทำให้สิ่งที่ซับซ้อนกว่านั้นสำหรับผู้คนที่จะเข้าใจด้วยคำศัพท์เช่น "รูปแบบปกติที่อ่อนแอ", "เข้มงวด" เป็นต้น ถ้าฉันเข้าใจคุณถูกต้องมันฟังดูเหมือน! โอเปอเรเตอร์หมายถึงการเก็บค่าที่ประเมินของนิพจน์แทนที่จะเก็บบล็อกนิรนามเพื่อประเมินในภายหลัง นั่นเป็นการตีความที่สมเหตุสมผลหรือมีอะไรมากกว่านั้นใช่ไหม
David

71
@David คำถามคือไกลประเมินค่า รูปแบบปกติที่อ่อนแอหัวหมายถึง: ประเมินมันจนกว่าคุณจะไปถึงตัวสร้างชั้นนอกสุด รูปแบบปกติหมายถึงประเมินมูลค่าทั้งหมดจนกว่าจะไม่มีส่วนประกอบที่ไม่ได้ประเมินค่า เนื่องจาก Haskell อนุญาตให้มีระดับความลึกในการประเมินผลทุกประเภทจึงมีคำศัพท์มากมายเพื่ออธิบายสิ่งนี้ คุณมักจะไม่พบความแตกต่างเหล่านี้ในภาษาที่รองรับเฉพาะความหมายการโทรตามค่า
Don Stewart

8
@ David: ฉันเขียนคำอธิบายในเชิงลึกเพิ่มเติมได้ที่นี่: Haskell: รูปแบบปกติที่อ่อนแอคืออะไร? . seqแต่ผมไม่ได้กล่าวถึงรูปแบบปังหรือคำอธิบายประกอบเข้มงวดพวกเขาจะเทียบเท่ากับการใช้
hammar

1
เพียงเพื่อให้แน่ใจว่าฉันเข้าใจ: ความเกียจคร้านนั้นเกี่ยวข้องกับการโต้แย้งเวลาที่ไม่รู้จักหรือไม่? คือถ้าซอร์สโค้ดมีคำสั่งจริง ๆ(2 + 2)มันจะยังคงได้รับการปรับให้เป็น4แทนที่จะมีรหัสเพื่อเพิ่มหมายเลขที่รู้จักหรือไม่?
สวัสดีแองเจิล

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

92

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

data Foo = Foo Int !Int

first (Foo x _) = x
second (Foo _ y) = y

เนื่องจากข้อโต้แย้งที่ไม่เข้มงวดไม่ได้รับการประเมินโดยการsecondส่งผ่านundefinedจึงไม่ทำให้เกิดปัญหา:

> second (Foo undefined 1)
1

แต่อาร์กิวเมนต์ที่เข้มงวดไม่สามารถทำได้undefinedแม้ว่าเราจะไม่ใช้ค่า:

> first (Foo 1 undefined)
*** Exception: Prelude.undefined

2
นี่ดีกว่าคำตอบของ Curt Sampson เพราะจริง ๆ แล้วมันอธิบายถึงผลกระทบที่ผู้ใช้สังเกตได้!สัญลักษณ์ที่มีอยู่แทนที่จะเจาะลึกลงไปในรายละเอียดการใช้งานภายใน
David Grayson

26

ฉันเชื่อว่ามันเป็นคำอธิบายประกอบที่เข้มงวด

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

มีข้อมูลเพิ่มเติมเกี่ยวกับหน้านี้เป็น: ผลการดำเนินงาน / เข้มงวด


อืมตัวอย่างในหน้านั้นที่คุณอ้างถึงดูเหมือนจะพูดถึงการใช้! เมื่อเรียกใช้ฟังก์ชัน แต่ดูเหมือนว่าจะแตกต่างจากการประกาศในประเภท ฉันพลาดอะไรไป
David

การสร้างอินสแตนซ์ของประเภทก็เป็นนิพจน์เช่นกัน คุณสามารถนึกถึงตัวสร้างประเภทเป็นฟังก์ชันที่คืนค่าอินสแตนซ์ใหม่ของชนิดที่ระบุให้กับอาร์กิวเมนต์ที่ให้มา
344 Chris Chris Vest

4
ในความเป็นจริงสำหรับทุกเจตนาและวัตถุประสงค์ตัวสร้างชนิดเป็นฟังก์ชัน คุณสามารถนำไปใช้บางส่วนส่งผ่านไปยังฟังก์ชั่นอื่น ๆ (เช่นmap Just [1,2,3]เพื่อรับ [เพียงแค่ 1 แค่ 2 เพียงแค่ 3]) และอื่น ๆ ฉันคิดว่ามันมีประโยชน์ที่จะคิดว่าความสามารถในการจับคู่รูปแบบกับพวกเขารวมถึงสิ่งอำนวยความสะดวกที่ไม่เกี่ยวข้องอย่างสมบูรณ์
cjs
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.