GADT จัดเตรียมไวยากรณ์ที่ชัดเจนและดีกว่าให้กับโค้ดโดยใช้ประเภทที่มีอยู่โดยจัดเตรียมให้โดยนัยสำหรับ forall
ฉันคิดว่ามีข้อตกลงทั่วไปว่าไวยากรณ์ GADT ดีกว่า ฉันจะไม่บอกว่าเป็นเพราะ GADT ให้การบอกทางโดยปริยาย แต่เนื่องจากไวยากรณ์ดั้งเดิมที่เปิดใช้งานกับExistentialQuantification
ส่วนขยายนั้นอาจทำให้สับสน / ทำให้เข้าใจผิด แน่นอนว่าไวยากรณ์นั้นมีลักษณะดังนี้:
data SomeType = forall a. SomeType a
หรือมีข้อ จำกัด :
data SomeShowableType = forall a. Show a => SomeShowableType a
และฉันคิดว่าฉันทามติคือการใช้คำหลักforall
ที่นี่ทำให้ประเภทสับสนได้ง่ายกับประเภทที่แตกต่างกันโดยสิ้นเชิง:
data AnyType = AnyType (forall a. a) -- need RankNTypes extension
ไวยากรณ์ที่ดีกว่าอาจใช้exists
คำหลักแยกต่างหากดังนั้นคุณจะต้องเขียน:
data SomeType = SomeType (exists a. a) -- not valid GHC syntax
ไวยากรณ์ของ GADT ไม่ว่าจะใช้กับแบบทางอ้อมหรือแบบชัดแจ้งforall
นั้นมีความเหมือนกันมากกว่าในทุกประเภทและดูเหมือนจะเข้าใจได้ง่ายขึ้น แม้ว่าจะมีforall
คำจำกัดความที่ชัดเจน แต่คำจำกัดความต่อไปนี้ก็ยังข้ามความคิดที่ว่าคุณสามารถรับค่าได้ทุกประเภทa
และวางไว้ใน monomorphic SomeType'
:
data SomeType' where
SomeType' :: forall a. (a -> SomeType') -- parentheses optional
และง่ายต่อการดูและเข้าใจความแตกต่างระหว่างประเภทนั้นและ:
data AnyType' where
AnyType' :: (forall a. a) -> AnyType'
ประเภทที่มีอยู่ดูเหมือนจะไม่สนใจในประเภทที่มีอยู่ แต่การจับคู่รูปแบบพวกเขาบอกว่ามีประเภทบางประเภทที่เราไม่ทราบว่าเป็นประเภทใดจนกระทั่งถึง & ยกเว้นว่าเราใช้ Typeable หรือ Data
เราใช้มันเมื่อเราต้องการซ่อนประเภท (เช่น: สำหรับรายการที่ต่างกัน) หรือเราไม่ทราบว่าประเภทใดในเวลารวบรวม
ฉันเดาว่าสิ่งเหล่านี้ไม่ไกลเกินไปแม้ว่าคุณไม่จำเป็นต้องใช้Typeable
หรือData
ใช้ชนิดที่มีอยู่ ฉันคิดว่ามันจะแม่นยำกว่าถ้าบอกว่าประเภทที่มีอยู่ให้ "กล่อง" ที่พิมพ์ได้ดีรอบประเภทที่ไม่ระบุ กล่องจะ "ซ่อน" ประเภทในแง่ที่ช่วยให้คุณสร้างรายการที่แตกต่างกันของกล่องดังกล่าวโดยไม่สนใจประเภทที่มีอยู่ ปรากฎว่าการดำรงอยู่ที่ไม่มีข้อ จำกัด เช่นเดียวกับSomeType'
ข้างบนนั้นค่อนข้างไร้ประโยชน์ แต่เป็นข้อ จำกัด ประเภท:
data SomeShowableType' where
SomeShowableType' :: forall a. (Show a) => a -> SomeShowableType'
ช่วยให้คุณสามารถจับคู่รูปแบบเพื่อดูภายใน "กล่อง" และทำให้สิ่งอำนวยความสะดวกชั้นเรียนมีให้บริการ
showIt :: SomeShowableType' -> String
showIt (SomeShowableType' x) = show x
โปรดทราบว่าการทำงานนี้สำหรับการเรียนประเภทใด ๆ ที่ไม่เพียงหรือTypeable
Data
สำหรับความสับสนของคุณเกี่ยวกับหน้า 20 ของเด็คสไลด์ผู้เขียนบอกว่ามันเป็นไปไม่ได้สำหรับฟังก์ชั่นที่มีอยู่ Worker
เพื่อเรียกร้องให้Worker
มีBuffer
อินสแตนซ์เฉพาะ คุณสามารถเขียนฟังก์ชั่นเพื่อสร้างประเภทการWorker
ใช้งานBuffer
เช่นMemoryBuffer
:
class Buffer b where
output :: String -> b -> IO ()
data Worker x = forall b. Buffer b => Worker {buffer :: b, input :: x}
data MemoryBuffer = MemoryBuffer
instance Buffer MemoryBuffer
memoryWorker = Worker MemoryBuffer (1 :: Int)
memoryWorker :: Worker Int
แต่ถ้าคุณเขียนฟังก์ชั่นที่รับWorker
อาร์กิวเมนต์มันจะสามารถใช้Buffer
สิ่งอำนวยความสะดวกระดับประเภททั่วไปเท่านั้น(เช่นฟังก์ชั่นoutput
):
doWork :: Worker Int -> IO ()
doWork (Worker b x) = output (show x) b
ไม่สามารถลองใช้ความต้องการที่b
เป็นบัฟเฟอร์ชนิดใดประเภทหนึ่งได้แม้ผ่านการจับคู่รูปแบบ:
doWorkBroken :: Worker Int -> IO ()
doWorkBroken (Worker b x) = case b of
MemoryBuffer -> error "try this" -- type error
_ -> error "try that"
ในที่สุดข้อมูลรันไทม์เกี่ยวกับประเภทที่มีอยู่จะมีให้ผ่านอาร์กิวเมนต์ "พจนานุกรม" โดยนัยสำหรับประเภทข้อมูลที่เกี่ยวข้อง Worker
ประเภทข้างต้นใน addtion จะมีฟิลด์สำหรับบัฟเฟอร์และใส่นอกจากนี้ยังมีสนามนัยมองไม่เห็นว่าจุดที่จะBuffer
พจนานุกรม (คล้ายตาราง v แม้ว่ามันจะใหญ่แทบจะไม่เป็นมันก็มีตัวชี้ไปยังที่เหมาะสมoutput
ฟังก์ชั่น)
ภายในคลาสชนิดBuffer
ถูกแสดงเป็นชนิดข้อมูลที่มีเขตข้อมูลฟังก์ชันและอินสแตนซ์คือ "พจนานุกรม" ประเภทนี้:
data Buffer' b = Buffer' { output' :: String -> b -> IO () }
dBuffer_MemoryBuffer :: Buffer' MemoryBuffer
dBuffer_MemoryBuffer = Buffer' { output' = undefined }
ประเภทที่มีอยู่มีฟิลด์ที่ซ่อนอยู่สำหรับพจนานุกรมนี้:
data Worker' x = forall b. Worker' { dBuffer :: Buffer' b, buffer' :: b, input' :: x }
และฟังก์ชั่นเช่นนี้doWork
ที่ทำงานกับWorker'
ค่าที่มีอยู่จะถูกนำไปใช้เป็น:
doWork' :: Worker' Int -> IO ()
doWork' (Worker' dBuf b x) = output' dBuf (show x) b
สำหรับคลาสประเภทที่มีเพียงฟังก์ชันเดียวพจนานุกรมจะถูกปรับให้เหมาะกับประเภทใหม่ดังนั้นในตัวอย่างนี้Worker
ประเภทที่มีอยู่จะรวมเขตข้อมูลที่ซ่อนอยู่ซึ่งประกอบด้วยตัวชี้ฟังก์ชันไปยังoutput
ฟังก์ชันสำหรับบัฟเฟอร์และนั่นเป็นเพียงข้อมูลรันไทม์ที่จำเป็นเท่านั้น doWork
โดย