นั่น@nเป็นคุณลักษณะขั้นสูงของ Haskell ทันสมัยซึ่งโดยทั่วไปแล้วจะไม่ครอบคลุมโดยบทเรียนเช่น LYAH และไม่สามารถพบรายงานได้
มันเรียกว่าแอปพลิเคชันประเภทและเป็นส่วนขยายภาษา GHC เพื่อความเข้าใจลองพิจารณาฟังก์ชั่น polymorphic แบบง่าย ๆ
dup :: forall a . a -> (a, a)
dup x = (x, x)
การเรียกใช้งานได้โดยสัญชาตญาณdupดังนี้:
- โทรเลือกประเภท
a
- โทรเลือกมูลค่า
xของชนิดได้รับการแต่งตั้งก่อนหน้านี้a
dup จากนั้นตอบด้วยค่าประเภท (a,a)
ในความรู้สึกdupเวลาสองขัดแย้ง: ประเภทและความคุ้มค่าa x :: aอย่างไรก็ตาม GHC มักจะสามารถอนุมานประเภทa(เช่นจากxหรือจากบริบทที่เรากำลังใช้งานอยู่dup) ดังนั้นเราจึงมักจะผ่านการโต้แย้งเพียงครั้งเดียวเพื่อdupxคือ ตัวอย่างเช่นเรามี
dup True :: (Bool, Bool)
dup "hello" :: (String, String)
...
ทีนี้ถ้าหากเราต้องการผ่านaอย่างชัดเจนล่ะ ในกรณีนี้เราสามารถเปิดTypeApplicationsส่วนขยายและเขียน
dup @Bool True :: (Bool, Bool)
dup @String "hello" :: (String, String)
...
หมายเหตุ@...ข้อโต้แย้งที่ถือประเภท (ไม่ใช่ค่า) สิ่งเหล่านี้เป็นสิ่งที่มีอยู่ในเวลารวบรวมเท่านั้น - ณ เวลาอาร์กิวเมนต์ไม่มีอยู่
ทำไมเราต้องการสิ่งนั้น ดีบางครั้งไม่มีรอบและเราต้องการที่จะแยงคอมไพเลอร์ที่จะเลือกที่เหมาะสมx aเช่น
dup @Bool :: Bool -> (Bool, Bool)
dup @String :: String -> (String, String)
...
แอปพลิเคชันประเภทมักมีประโยชน์เมื่อใช้ร่วมกับส่วนขยายอื่น ๆ ซึ่งทำให้การอนุมานประเภทเป็นไปไม่ได้สำหรับ GHC เช่นประเภทที่ไม่ชัดเจนหรือตระกูลประเภท ฉันจะไม่พูดถึงสิ่งเหล่านั้น แต่คุณสามารถเข้าใจได้ว่าบางครั้งคุณจำเป็นต้องช่วยคอมไพเลอร์โดยเฉพาะอย่างยิ่งเมื่อใช้ฟีเจอร์ระดับพิมพ์ที่มีประสิทธิภาพ
ตอนนี้เกี่ยวกับกรณีเฉพาะของคุณ ฉันไม่ได้มีรายละเอียดทั้งหมดผมไม่ทราบว่าห้องสมุด แต่ก็มีโอกาสมากที่คุณnหมายถึงชนิดของมูลค่าจำนวนธรรมชาติในระดับชนิด ที่นี่เรากำลังดำน้ำในส่วนขยายค่อนข้างสูงเช่นที่กล่าวมาข้างต้นบวกDataKindsอาจGADTsและเครื่องจักรพิมพ์ดีดบางส่วน แม้ว่าฉันจะไม่สามารถอธิบายทุกสิ่งได้ แต่หวังว่าฉันจะสามารถให้ข้อมูลเชิงลึกบางอย่างได้ สังหรณ์ใจ
foo :: forall n . some type using n
ใช้เวลาเป็นอาร์กิวเมนต์@nชนิดของการคอมไพล์เวลาธรรมชาติซึ่งไม่ได้ผ่านที่รันไทม์ แทน,
foo :: forall n . C n => some type using n
ใช้เวลา@n(เวลารวบรวม) ร่วมกับหลักฐานที่น่าพอใจข้อ จำกัดn หลังเป็นอาร์กิวเมนต์เวลาทำงานซึ่งอาจจะเปิดเผยมูลค่าที่แท้จริงของC n nในกรณีของคุณฉันคิดว่าคุณมีบางสิ่งที่คล้ายราง
value :: forall n . Reflects n Int => Int
ซึ่งช่วยให้รหัสเพื่อนำธรรมชาติระดับประเภทไปยังคำศัพท์ระดับการเข้าถึง "ประเภท" เป็น "ค่า" (ประเภทข้างต้นถือว่าเป็น "ไม่ชัดเจน" อย่างใดอย่างหนึ่งโดยวิธี - คุณจำเป็นต้องมี@ndisambiguate)
ในที่สุด: ทำไมเราจึงควรต้องการผ่านnระดับประเภทหากเราในภายหลังแปลงเป็นระดับคำ คงไม่ง่ายกว่าที่จะเขียนฟังก์ชั่นเช่น
foo :: Int -> ...
foo n ... = ... use n
แทนที่จะยุ่งยากมากขึ้น
foo :: forall n . Reflects n Int => ...
foo ... = ... use (value @n)
คำตอบที่ซื่อสัตย์คือ: ใช่มันจะง่ายขึ้น อย่างไรก็ตามการมีnระดับประเภทให้คอมไพเลอร์ทำการตรวจสอบแบบคงที่มากขึ้น ตัวอย่างเช่นคุณอาจต้องการประเภทที่จะเป็นตัวแทนของ "จำนวนเต็ม modulo n" และอนุญาตให้เพิ่มประเภท มี
data Mod = Mod Int -- Int modulo some n
foo :: Int -> Mod -> Mod -> Mod
foo n (Mod x) (Mod y) = Mod ((x+y) `mod` n)
ทำงาน แต่ไม่มีการตรวจสอบว่าxและyมีโมดูลัสเดียวกัน เราอาจเพิ่มแอปเปิ้ลและส้มถ้าเราไม่ระวัง เราสามารถเขียนแทน
data Mod n = Mod Int -- Int modulo n
foo :: Int -> Mod n -> Mod n -> Mod n
foo n (Mod x) (Mod y) = Mod ((x+y) `mod` n)
ซึ่งดีกว่า แต่ยังคงสามารถโทรfoo 5 x yได้แม้ในขณะที่nไม่5อยู่ ไม่ดี. แทน,
data Mod n = Mod Int -- Int modulo n
-- a lot of type machinery omitted here
foo :: forall n . SomeConstraint n => Mod n -> Mod n -> Mod n
foo (Mod x) (Mod y) = Mod ((x+y) `mod` (value @n))
ป้องกันสิ่งต่าง ๆ ที่จะผิดไป คอมไพเลอร์ตรวจสอบทุกอย่างแบบคงที่ รหัสนี้ใช้งานได้ยาก แต่ในแง่ที่ทำให้ใช้งานได้ยากกว่าคือทั้งหมด: เราต้องการทำให้ผู้ใช้ไม่สามารถลองเพิ่มมอดุลัสที่ผิดได้
สรุป: นี่คือส่วนขยายขั้นสูงมาก หากคุณเป็นมือใหม่คุณจะต้องพัฒนาเทคนิคเหล่านี้อย่างช้าๆ อย่าท้อใจถ้าคุณไม่สามารถเข้าใจพวกเขาหลังจากการศึกษาสั้น ๆ มันใช้เวลาพอสมควร ทำทีละขั้นตอนเล็ก ๆ แก้แบบฝึกหัดให้แต่ละคุณสมบัติเข้าใจจุดนั้น และคุณจะมี StackOverflow เสมอเมื่อคุณติดขัด :-)