นั่น@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
) ดังนั้นเราจึงมักจะผ่านการโต้แย้งเพียงครั้งเดียวเพื่อdup
x
คือ ตัวอย่างเช่นเรามี
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
ซึ่งช่วยให้รหัสเพื่อนำธรรมชาติระดับประเภทไปยังคำศัพท์ระดับการเข้าถึง "ประเภท" เป็น "ค่า" (ประเภทข้างต้นถือว่าเป็น "ไม่ชัดเจน" อย่างใดอย่างหนึ่งโดยวิธี - คุณจำเป็นต้องมี@n
disambiguate)
ในที่สุด: ทำไมเราจึงควรต้องการผ่าน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 เสมอเมื่อคุณติดขัด :-)