ที่นี่ "ค่านิยม" "ประเภท" และ "ชนิด" มีความหมายอย่างเป็นทางการดังนั้นเมื่อพิจารณาการใช้ภาษาอังกฤษทั่วไปหรือการเปรียบเทียบกับการจำแนกประเภทรถยนต์จะทำให้คุณมาถึง
คำตอบของฉันเกี่ยวข้องกับความหมายอย่างเป็นทางการของข้อกำหนดเหล่านี้ในบริบทของ 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 นั้นเต็มไปด้วยความขัดแย้งอยู่แล้วเนื่องจากการไม่เลิกจ้างดังนั้นจึงไม่ถือว่าเป็นเรื่องใหญ่