ที่นี่ "ค่านิยม" "ประเภท" และ "ชนิด" มีความหมายอย่างเป็นทางการดังนั้นเมื่อพิจารณาการใช้ภาษาอังกฤษทั่วไปหรือการเปรียบเทียบกับการจำแนกประเภทรถยนต์จะทำให้คุณมาถึง
คำตอบของฉันเกี่ยวข้องกับความหมายอย่างเป็นทางการของข้อกำหนดเหล่านี้ในบริบทของ Haskell โดยเฉพาะ ความหมายเหล่านี้มีพื้นฐานมาจาก (แม้ว่าจะไม่เหมือนกันจริง ๆ ) ความหมายที่ใช้ในทางคณิตศาสตร์ / CS "ทฤษฎีประเภท" ดังนั้นนี่จะไม่ใช่คำตอบที่ดีมาก "วิทยาการคอมพิวเตอร์" แต่ควรเป็นคำตอบที่ดีสำหรับ Haskell
ใน Haskell (และภาษาอื่น ๆ ) มันจะมีประโยชน์ในการกำหนดประเภทให้กับนิพจน์โปรแกรมที่อธิบายคลาสของค่าที่นิพจน์ได้รับอนุญาต ฉันคิดว่าที่นี่คุณได้เห็นตัวอย่างเพียงพอที่จะเข้าใจว่าทำไมมันจะมีประโยชน์ที่จะรู้ว่าในการแสดงออกsqrt (a**2 + b**2)ตัวแปรaและbจะเป็นค่าประเภทเสมอDoubleและไม่พูดStringและBoolตามลำดับ โดยทั่วไปมีประเภทช่วยให้เราในการเขียนการแสดงออก / โปรแกรมที่จะทำงานได้อย่างถูกต้องในช่วงกว้างของค่า
ทีนี้บางสิ่งที่คุณอาจไม่เคยรู้มาก่อนก็คือประเภท Haskell เช่นเดียวกับที่ปรากฏในลายเซ็นประเภท:
fmap :: Functor f => (a -> b) -> f a -> f b
จริง ๆ แล้วตัวเองเขียนในภาษาย่อยระดับ Haskell ข้อความของโปรแกรมFunctor f => (a -> b) -> f a -> f bคือ - ค่อนข้างแท้จริง - นิพจน์ประเภทที่เขียนในภาษาย่อยนี้ sublanguage รวมถึงผู้ประกอบการ (เช่น->เป็นผู้ประกอบการมัดขวาเชื่อมโยงในภาษานี้) ตัวแปร (เช่นf, aและb) และ "แอพลิเคชัน" ของการแสดงออกประเภทหนึ่งไปยังอีก (เช่นf aถูกfนำไปใช้a)
ฉันพูดถึงว่ามีประโยชน์ในหลาย ๆ ภาษาในการกำหนดประเภทให้กับนิพจน์ของโปรแกรมเพื่ออธิบายคลาสของค่านิพจน์หรือไม่ ใน sublanguage ระดับประเภทนี้นิพจน์จะประเมินเป็นประเภท (แทนที่จะเป็นค่า ) และท้ายที่สุดการมีประโยชน์ในการกำหนดประเภทให้กับนิพจน์เพื่ออธิบายประเภทของประเภทที่พวกเขาได้รับอนุญาตให้แสดง โดยทั่วไปมีหลายชนิดช่วยให้เราในการเขียนการแสดงออกประเภทที่จะทำงานได้อย่างถูกต้องในช่วงกว้างของประเภท
ดังนั้นค่าจะประเภทเป็นชนิดจะชนิดและประเภทช่วยให้เราเขียนค่าโปรแกรมระดับพื้นดินในขณะที่ชนิดช่วยให้เราเขียนพิมพ์โปรแกรมระดับพื้นดิน
สิ่งใดที่เหล่านี้ชนิดมีลักษณะอย่างไร ลองพิจารณาประเภทของลายเซ็น:
id :: a -> a
หากแสดงออกประเภทa -> aคือการถูกต้องสิ่งที่ชนิดของประเภทที่เราควรจะอนุญาตให้มีตัวแปรaจะเป็น? นิพจน์ประเภท:
Int -> Int
Bool -> Bool
มีลักษณะที่ถูกต้องดังนั้นประเภท IntและBoolจะเห็นได้ชัดทางด้านขวาชนิด แต่ประเภทที่ซับซ้อนยิ่งขึ้นเช่น:
[Double] -> [Double]
Maybe [(Double,Int)] -> Maybe [(Double,Int)]
ดูถูกต้อง ในความเป็นจริงเนื่องจากเราควรจะสามารถเรียกidใช้ฟังก์ชันได้:
(a -> a) -> (a -> a)
ดูดี ดังนั้นInt, Bool, [Double], Maybe [(Double,Int)]และa -> aรูปลักษณ์ทั้งหมดเช่นประเภทของสิทธิชนิด
ในคำอื่น ๆ ที่ดูเหมือนว่ามีเพียงหนึ่งชนิดขอเรียกมัน*เหมือนตัวแทน Unix และทุกประเภทมีเดียวกันชนิด * , ตอนจบของเรื่อง
ขวา?
ก็ไม่มาก ปรากฎว่าMaybeทั้งหมดโดยตัวมันเองเป็นเพียงการแสดงออกประเภทที่ถูกต้องเป็นMaybe Int(ในทางเดียวกันsqrtมากโดยตัวเองทั้งหมดเป็นเพียงการแสดงออกที่ถูกต้องค่าเป็นsqrt 25) อย่างไรก็ตามการแสดงออกประเภทต่อไปนี้ไม่ถูกต้อง:
Maybe -> Maybe
เนื่องจากในขณะที่Maybeเป็นนิพจน์ประเภทจึงไม่ได้แสดงประเภทของประเภทที่สามารถมีค่าได้ ดังนั้นนั่นคือวิธีการที่เราควรกำหนด*- เป็นชนิดของประเภทที่มีค่า; มันรวมถึงประเภท "สมบูรณ์" ชอบDoubleหรือแต่ไม่รวมที่ไม่สมบูรณ์แบบไร้ค่าเช่นMaybe [(Double,Int)] Either Stringเพื่อความเรียบง่ายฉันจะเรียก*"คอนกรีตประเภท" ประเภทนี้อย่างสมบูรณ์แม้ว่าคำศัพท์นี้จะไม่เป็นสากลและ "คอนกรีตประเภท" อาจหมายถึงสิ่งที่แตกต่างจากโปรแกรมเมอร์ C ++
ตอนนี้ในการแสดงออกของชนิดa -> aตราบเท่าที่ประเภทaมีชนิด * (ชนิดของประเภทคอนกรีต) ผลของการแสดงออกประเภทที่a -> aจะยังมีชนิด * (เช่นชนิดของประเภทคอนกรีต)
ดังนั้นสิ่งที่ชนิดของประเภทคือMaybe? ดีMaybeสามารถนำไปใช้ประเภทคอนกรีตเพื่อให้คอนกรีตชนิดอื่น ดังนั้นMaybeดูเหมือนเล็กน้อยเช่นฟังก์ชั่นประเภทในระดับที่ใช้เป็นชนิดของชนิด *และผลตอบแทนประเภทของชนิด *ถ้าเรามีฟังก์ชั่นระดับค่าที่เอาค่าของประเภท Intและส่งกลับค่าของประเภท Intที่เราต้องการให้มันเป็นประเภทลายเซ็นInt -> Intดังนั้นโดยการเปรียบเทียบเราควรจะให้ชนิดลายเซ็น GHCi เห็นด้วย:Maybe* -> *
> :kind Maybe
Maybe :: * -> *
กลับไปที่:
fmap :: Functor f => (a -> b) -> f a -> f b
ในลายเซ็นของประเภทนี้ตัวแปรfมีชนิด* -> *และตัวแปรaและbมีชนิด*; ตัวดำเนินการในตัว->มีชนิด* -> * -> *(ใช้ชนิดของชนิด*ด้านซ้ายและด้านขวาและส่งชนิดชนิด*) จากนี้และกฎระเบียบของการอนุมานชนิดที่คุณสามารถอนุมานได้ว่าa -> bเป็นชนิดที่ถูกต้องกับทุกชนิด*, f aและf bนอกจากนี้ยังมีประเภทที่ถูกต้องกับทุกชนิด*และเป็นประเภทที่ถูกต้องของชนิด(a -> b) -> f a -> f b*
กล่าวอีกนัยหนึ่งคอมไพเลอร์สามารถ "ตรวจสอบชนิด" นิพจน์ประเภท(a -> b) -> f a -> f bเพื่อตรวจสอบว่ามันถูกต้องสำหรับตัวแปรประเภทของชนิดที่ถูกต้องเช่นเดียวกับ "ตรวจสอบประเภท" sqrt (a**2 + b**2)เพื่อตรวจสอบว่าถูกต้องสำหรับตัวแปรประเภทที่เหมาะสม
เหตุผลในการใช้คำที่แยกต่างหากสำหรับ "ประเภท" กับ "ชนิด" (กล่าวคือไม่ได้พูดถึง "ประเภทประเภท") ส่วนใหญ่เป็นเพียงเพื่อหลีกเลี่ยงความสับสน ชนิดข้างต้นดูแตกต่างจากชนิดและอย่างน้อยในตอนแรกดูเหมือนจะประพฤติค่อนข้างแตกต่างกัน (ยกตัวอย่างเช่นมันต้องใช้เวลาที่จะตัดหัวของรอบความคิดที่ว่าทุกประเภท "ปกติ" มีชนิดเดียวกัน*และชนิดของการa -> bเป็น*ไม่ได้* -> *.)
บางส่วนนี้เป็นประวัติศาสตร์ เมื่อ GHC Haskell มีวิวัฒนาการความแตกต่างระหว่างค่าประเภทและชนิดเริ่มเบลอ วันนี้ค่าสามารถ "เลื่อน" เป็นประเภทและประเภทและชนิดเป็นสิ่งเดียวกัน ดังนั้นใน Haskell สมัยใหม่ค่านิยมทั้งสองประเภทและประเภทARE (เกือบ) และประเภทประเภทเป็นประเภทเพิ่มเติม
@ user21820 ถามถึงคำอธิบายเพิ่มเติมของ "ประเภทและชนิดเป็นสิ่งเดียวกัน" เพื่อความชัดเจนเล็กน้อยใน GHC Haskell สมัยใหม่ (ตั้งแต่เวอร์ชั่น 8.0.1 ฉันคิดว่า) ประเภทและชนิดนั้นได้รับการปฏิบัติเหมือนกันในคอมไพเลอร์โค้ดส่วนใหญ่ คอมไพเลอร์ใช้ความพยายามในข้อความแสดงข้อผิดพลาดเพื่อแยกความแตกต่างระหว่าง "ประเภท" และ "ชนิด" ขึ้นอยู่กับว่ามันบ่นเกี่ยวกับประเภทของค่าหรือประเภทของประเภทตามลำดับ
นอกจากนี้หากไม่มีการเปิดใช้งานส่วนขยายจะสามารถแยกความแตกต่างได้ง่ายในภาษาพื้นผิว ตัวอย่างเช่นประเภท (ของค่า) มีการแสดงในไวยากรณ์ (เช่นในลายเซ็นประเภท) แต่ประเภท (ประเภท) คือ - ฉันคิดว่า - โดยนัยอย่างสมบูรณ์และไม่มีไวยากรณ์ที่ชัดเจนที่พวกเขาปรากฏ
แต่ถ้าคุณเปิดส่วนขยายที่เหมาะสมความแตกต่างระหว่างประเภทและชนิดส่วนใหญ่จะหายไป ตัวอย่างเช่น:
{-# LANGUAGE GADTs, TypeInType #-}
data Foo where
Bar :: Bool -> * -> Foo
นี่Barคือ (ทั้งค่าและ) ประเภท ในฐานะที่เป็นประเภทชนิดของมันBool -> * -> Fooซึ่งเป็นฟังก์ชั่นประเภทในระดับที่ใช้ประเภทของชนิดBool(ซึ่งเป็นชนิด แต่ยังชนิด) และชนิดของชนิดและผลิตประเภทของชนิด* Fooดังนั้น:
type MyBar = Bar True Int
ตรวจสอบชนิดอย่างถูกต้อง
ในฐานะที่เป็น @AndrejBauer อธิบายในคำตอบของเขาความล้มเหลวในการแยกแยะความแตกต่างระหว่างประเภทและชนิดนี้ไม่ปลอดภัย - การมีประเภท / ชนิด*ที่มีประเภท / ชนิดเป็นตัวเอง (ซึ่งเป็นกรณีใน Haskell ทันสมัย) นำไปสู่ความขัดแย้ง อย่างไรก็ตามระบบประเภทของ Haskell นั้นเต็มไปด้วยความขัดแย้งอยู่แล้วเนื่องจากการไม่เลิกจ้างดังนั้นจึงไม่ถือว่าเป็นเรื่องใหญ่