Haskell Type vs Data Constructor


124

ผมเรียนรู้จาก Haskell learnyouahaskell.com ฉันมีปัญหาในการทำความเข้าใจตัวสร้างประเภทและตัวสร้างข้อมูล ตัวอย่างเช่นฉันไม่ค่อยเข้าใจความแตกต่างระหว่างสิ่งนี้:

data Car = Car { company :: String  
               , model :: String  
               , year :: Int  
               } deriving (Show) 

และนี่:

data Car a b c = Car { company :: a  
                     , model :: b  
                     , year :: c   
                     } deriving (Show)  

ผมเข้าใจว่าคนแรกที่เป็นเพียงการใช้หนึ่งคอนสตรัค ( Car) Carข้อมูลที่สร้างขึ้นจากประเภท ฉันไม่เข้าใจข้อสองจริงๆ

นอกจากนี้ประเภทข้อมูลกำหนดไว้เช่นนี้ได้อย่างไร:

data Color = Blue | Green | Red

พอดีกับทั้งหมดนี้หรือไม่

จากสิ่งที่ฉันเข้าใจตัวอย่างที่สาม ( Color) เป็นชนิดที่สามารถอยู่ในสามรัฐ: Blue, หรือGreen Redแต่นั่นขัดแย้งกับวิธีที่ฉันเข้าใจสองตัวอย่างแรกคือประเภทCarสามารถอยู่ในสถานะเดียวCarซึ่งสามารถใช้พารามิเตอร์ต่างๆในการสร้างได้หรือไม่? ถ้าเป็นเช่นนั้นตัวอย่างที่สองจะเข้ากันได้อย่างไร?

โดยพื้นฐานแล้วฉันกำลังมองหาคำอธิบายที่รวมตัวอย่าง / โครงสร้างโค้ดสามข้อข้างต้น


18
ตัวอย่างรถของคุณอาจจะสับสนเล็กน้อยเนื่องจากCarเป็นทั้งตัวสร้างประเภท (ทางด้านซ้ายของ=) และตัวสร้างข้อมูล (ทางด้านขวา) ในตัวอย่างแรกตัวCarสร้างประเภทจะไม่มีอาร์กิวเมนต์ในตัวอย่างที่สองจะใช้เวลาสาม ในทั้งสองตัวอย่างตัวCarสร้างข้อมูลรับอาร์กิวเมนต์สามตัว (แต่ประเภทของอาร์กิวเมนต์เหล่านั้นอยู่ในกรณีเดียวคงที่และในพารามิเตอร์อื่น ๆ )
Simon Shine

คนแรกที่เป็นเพียงการใช้ตัวสร้างข้อมูล ( Car :: String -> String -> Int -> Car) Carเพื่อสร้างข้อมูลประเภท ที่สองเป็นเพียงการใช้ข้อมูลที่เป็นหนึ่งในคอนสตรัค ( Car :: a -> b -> c -> Car a b c) Car a b cเพื่อสร้างข้อมูลประเภท
Will Ness

คำตอบ:


228

ในการdataประกาศตัวสร้างประเภทคือสิ่งที่อยู่ทางซ้ายมือของเครื่องหมายเท่ากับ ตัวสร้างข้อมูลคือสิ่งที่อยู่ทางขวามือของเครื่องหมายเท่ากับ คุณใช้ตัวสร้างประเภทที่คาดว่าจะมีประเภทและคุณใช้ตัวสร้างข้อมูลที่คาดว่าจะมีค่า

ตัวสร้างข้อมูล

เพื่อให้ง่ายขึ้นเราสามารถเริ่มต้นด้วยตัวอย่างของประเภทที่แสดงถึงสี

data Colour = Red | Green | Blue

ที่นี่เรามีตัวสร้างข้อมูลสามตัว Colourเป็นชนิดและเป็นตัวสร้างที่มีค่าของชนิดGreen ColourในทำนองเดียวกันRedและมีทั้งการก่อสร้างว่าค่าสร้างประเภทBlue Colourเราคงนึกภาพออกได้!

data Colour = RGB Int Int Int

เรายังคงมีเพียงประเภทColourแต่RGBไม่ใช่ค่า - เป็นฟังก์ชันที่ใช้สาม Ints และส่งคืนค่า! RGBมีประเภท

RGB :: Int -> Int -> Int -> Colour

RGBเป็นตัวสร้างข้อมูลที่เป็นฟังก์ชันที่รับค่าบางค่าเป็นอาร์กิวเมนต์จากนั้นใช้ค่าเหล่านั้นเพื่อสร้างค่าใหม่ หากคุณได้ทำการเขียนโปรแกรมเชิงวัตถุใด ๆ คุณควรตระหนักถึงสิ่งนี้ ใน OOP ตัวสร้างยังรับค่าบางค่าเป็นอาร์กิวเมนต์และส่งคืนค่าใหม่!

ในกรณีนี้ถ้าเราใช้RGBกับสามค่าเราจะได้ค่าสี!

Prelude> RGB 12 92 27
#0c5c1b

เราได้สร้างค่าของประเภทColourโดยใช้ตัวสร้างข้อมูล ตัวสร้างข้อมูลทั้งมีค่าเช่นตัวแปรจะหรือต้องใช้ค่าอื่น ๆ เป็นอาร์กิวเมนต์และสร้างใหม่ค่า หากคุณเคยเขียนโปรแกรมมาก่อนแนวคิดนี้ไม่น่าจะแปลกสำหรับคุณมากนัก

ช่วงระยะหยุดพัก

หากคุณต้องการสร้างต้นไม้ไบนารีเพื่อเก็บStrings คุณสามารถจินตนาการว่าจะทำสิ่งต่างๆเช่น

data SBTree = Leaf String
            | Branch String SBTree SBTree

สิ่งที่เราเห็นนี้คือประเภทSBTreeที่มีตัวสร้างข้อมูลสองตัว กล่าวอีกนัยหนึ่งมีสองฟังก์ชัน (คือLeafและBranch) ที่จะสร้างค่าของSBTreeประเภท หากคุณไม่คุ้นเคยกับการทำงานของต้นไม้ไบนารีเพียงแค่เข้าไปที่นั่น คุณไม่จำเป็นต้องรู้ว่าต้นไม้ไบนารีทำงานอย่างไรเพียงแต่ว่าต้นนี้เก็บStringไว้ในทางใดทางหนึ่ง

นอกจากนี้เรายังเห็นว่าตัวสร้างข้อมูลทั้งสองมีStringอาร์กิวเมนต์ - นี่คือ String ที่พวกเขาจะเก็บไว้ในแผนภูมิ

แต่! จะเป็นอย่างไรหากเราต้องการจัดเก็บBoolด้วยเราจะต้องสร้างต้นไม้ไบนารีใหม่ อาจมีลักษณะดังนี้:

data BBTree = Leaf Bool
            | Branch Bool BBTree BBTree

ประเภทตัวสร้าง

ทั้งสองSBTreeและBBTreeเป็นตัวสร้างประเภท แต่มีปัญหาที่ชัดเจน คุณเห็นไหมว่ามันคล้ายกันแค่ไหน? นั่นเป็นสัญญาณว่าคุณต้องการพารามิเตอร์ที่ไหนสักแห่ง

ดังนั้นเราสามารถทำได้:

data BTree a = Leaf a
             | Branch a (BTree a) (BTree a)

ตอนนี้เราแนะนำตัวแปร type aเป็นพารามิเตอร์ให้กับ type constructor ในคำประกาศนี้BTreeได้กลายเป็นฟังก์ชัน มันต้องใช้เวลาประเภทเป็นอาร์กิวเมนต์และมันส่งกลับใหม่ประเภท

มันเป็นสิ่งสำคัญที่นี่เพื่อพิจารณาความแตกต่างระหว่างที่ประเภทคอนกรีต (ตัวอย่าง ได้แก่Int, [Char]และMaybe Bool) ซึ่งเป็นชนิดที่สามารถกำหนดให้มีค่าในโปรแกรมของคุณและฟังก์ชั่นประเภทคอนสตรัคที่คุณจำเป็นต้องให้อาหารชนิดเพื่อให้สามารถที่จะ กำหนดให้กับค่า ค่าไม่สามารถเป็นประเภท "list" ได้เนื่องจากต้องเป็น "list of something " ในจิตวิญญาณเดียวกันค่าไม่สามารถเป็นประเภท "ต้นไม้ไบนารี" ได้เพราะจำเป็นต้องเป็น "ต้นไม้ไบนารีที่เก็บบางสิ่ง "

ถ้าเราส่งผ่านกล่าวว่าBoolเป็นอาร์กิวเมนต์BTreeมันจะส่งกลับประเภทBTree Boolซึ่งเป็นต้นไม้ไบนารีที่เก็บBools แทนที่ตัวแปร type ทุกครั้งaด้วย type Boolแล้วคุณจะเห็นเองว่ามันจริงแค่ไหน

หากคุณต้องการคุณสามารถดูBTreeเป็นฟังก์ชันด้วยชนิด

BTree :: * -> *

ชนิดค่อนข้างเหมือนประเภท - *หมายถึงประเภทคอนกรีตดังนั้นเราจึงกล่าวว่าBTreeมาจากประเภทคอนกรีตไปจนถึงประเภทคอนกรีต

ห่อ

ย้อนกลับมาที่นี่สักครู่และสังเกตความคล้ายคลึงกัน

  • คอนสตรัคข้อมูลคือ "ฟังก์ชั่น" ที่ใช้เวลา 0 หรือมากกว่าค่าและช่วยให้คุณกลับค่าใหม่

  • ตัวสร้างประเภทคือ "ฟังก์ชัน" ที่ใช้ 0 ประเภทขึ้นไปและให้คุณกลับมาเป็นประเภทใหม่

ตัวสร้างข้อมูลที่มีพารามิเตอร์นั้นยอดเยี่ยมหากเราต้องการให้ค่าของเราเปลี่ยนแปลงเล็กน้อย - เราใส่ความแปรผันเหล่านั้นในพารามิเตอร์และปล่อยให้คนที่สร้างค่าเป็นผู้ตัดสินใจว่าพวกเขาจะใส่อาร์กิวเมนต์ใดในแง่เดียวกันการพิมพ์ตัวสร้างที่มีพารามิเตอร์ก็เจ๋ง หากเราต้องการความแตกต่างเล็กน้อยในประเภทของเรา! เราใส่รูปแบบเหล่านั้นเป็นพารามิเตอร์และปล่อยให้คนที่สร้างประเภทตัดสินใจว่าพวกเขาจะใส่อาร์กิวเมนต์อะไร

กรณีศึกษา

ในฐานะที่เป็นบ้านที่นี่เราสามารถพิจารณาMaybe aประเภทได้ คำจำกัดความของมันคือ

data Maybe a = Nothing
             | Just a

นี่Maybeคือตัวสร้างชนิดที่ส่งคืนประเภทคอนกรีต Justเป็นตัวสร้างข้อมูลที่ส่งคืนค่า Nothingเป็นตัวสร้างข้อมูลที่มีค่า ถ้าเราดูประเภทของJustเราจะเห็นว่า

Just :: a -> Maybe a

ในคำอื่น ๆJustใช้ค่าของชนิดและผลตอบแทนค่าของชนิดa Maybe aถ้าเราดูประเภทของMaybeเราจะเห็นว่า

Maybe :: * -> *

กล่าวอีกนัยหนึ่งคือMaybeรับประเภทคอนกรีตและส่งคืนประเภทคอนกรีต

อีกครั้ง! ความแตกต่างระหว่างประเภทคอนกรีตและฟังก์ชันตัวสร้างประเภท คุณไม่สามารถสร้างรายการMaybes ได้หากคุณพยายามดำเนินการ

[] :: [Maybe]

คุณจะได้รับข้อผิดพลาด แต่คุณสามารถสร้างรายการหรือMaybe Int Maybe aนั่นเป็นเพราะMaybeเป็นฟังก์ชันตัวสร้างชนิด แต่รายการต้องมีค่าของประเภทคอนกรีต Maybe IntและMaybe aเป็นประเภทคอนกรีต (หรือถ้าคุณต้องการให้เรียกใช้ฟังก์ชันตัวสร้างที่ส่งคืนชนิดคอนกรีต)


2
ในตัวอย่างแรกของคุณทั้ง RED GREEN และ BLUE เป็นตัวสร้างที่ไม่มีข้อโต้แย้ง
OllieB

3
การอ้างว่าในdata Colour = Red | Green | Blue"เราไม่มีผู้สร้างเลย" นั้นผิดธรรมดา Type constructor และ data constructor ไม่จำเป็นต้องใช้อาร์กิวเมนต์โปรดดูเช่นhaskell.org/haskellwiki/Constructorซึ่งชี้ให้เห็นว่าในdata Tree a = Tip | Node a (Tree a) (Tree a)"มีตัวสร้างข้อมูล 2 ตัวคือ Tip และ Node"
Frerich Raabe

1
@CMCDragonkai คุณเป๊ะ! Kinds คือ "ประเภทของประเภท" วิธีการทั่วไปที่จะร่วมงานกับแนวความคิดของชนิดและค่าที่เรียกว่าพิมพ์ขึ้น Idrisเป็นภาษาที่พิมพ์ขึ้นโดยอาศัยแรงบันดาลใจจาก Haskell ด้วยส่วนขยาย GHC ที่ถูกต้องคุณยังสามารถเข้าใกล้การพิมพ์ที่ขึ้นอยู่กับ Haskell ได้ (บางคนล้อเล่นว่า "การวิจัยของ Haskell เป็นเรื่องเกี่ยวกับการหาว่าเราจะได้ประเภทที่พึ่งพาได้โดยไม่ต้องพึ่งพาประเภทใด")
kqr

1
@CMCDragonkai เป็นไปไม่ได้ที่จะมีการประกาศข้อมูลว่างเปล่าในมาตรฐาน Haskell แต่มีส่วนขยาย GHC ( -XEmptyDataDecls) ที่ให้คุณทำเช่นนั้นได้ เนื่องจากอย่างที่คุณบอกไม่มีค่าในประเภทนั้นฟังก์ชันf :: Int -> Zอาจไม่ส่งคืน (เพราะจะคืนค่าอะไร) อย่างไรก็ตามสามารถใช้ประโยชน์ได้เมื่อคุณต้องการประเภท แต่ไม่สนใจค่าจริงๆ
kqr

1
มันเป็นไปไม่ได้จริงๆเหรอ? ฉันเพิ่งลองใน GHC และมันทำงานโดยไม่มีข้อผิดพลาด ฉันไม่ต้องโหลดส่วนขยาย GHC ใด ๆ เพียงแค่วานิลลา GHC จากนั้นฉันก็เขียนได้:k Zและมันก็ให้ดาวแก่ฉัน
CMCDragonkai

42

Haskell มีประเภทข้อมูลเกี่ยวกับพีชคณิตซึ่งมีภาษาอื่น ๆ น้อยมาก นี่อาจเป็นสิ่งที่ทำให้คุณสับสน

ในภาษาอื่น ๆ คุณสามารถสร้าง "record", "struct" หรือที่คล้ายกันได้ซึ่งมีฟิลด์ที่มีชื่อมากมายซึ่งเก็บข้อมูลประเภทต่างๆ นอกจากนี้คุณยังสามารถบางครั้งทำให้ "การแจงนับ" ซึ่งมี (เล็ก) ชุดของค่าที่เป็นไปได้คงที่ (เช่นของคุณRed, GreenและBlue)

ใน Haskell คุณสามารถรวมทั้งสองอย่างนี้พร้อมกันได้ แปลก แต่จริง!

ทำไมจึงเรียกว่า "พีชคณิต"? ข้อมูลเชิงลึกพูดถึง "ประเภทผลรวม" และ "ประเภทผลิตภัณฑ์" ตัวอย่างเช่น:

data Eg1 = One Int | Two String

Eg1ค่าเป็นพื้นทั้งจำนวนเต็มหรือสตริง ดังนั้นชุดของEg1ค่าที่เป็นไปได้ทั้งหมดคือ "ผลรวม" ของชุดของค่าจำนวนเต็มที่เป็นไปได้ทั้งหมดและค่าสตริงที่เป็นไปได้ทั้งหมด ดังนั้นข้อมูลเชิงลึกจึงเรียกEg1ว่า "ประเภทผลรวม" ในทางกลับกัน:

data Eg2 = Pair Int String

ทุกEg2ค่าประกอบด้วยทั้งจำนวนเต็มและสตริง ดังนั้นเซตของEg2ค่าที่เป็นไปได้ทั้งหมดคือผลคูณคาร์ทีเซียนของเซตของจำนวนเต็มทั้งหมดและเซตของสตริงทั้งหมด ทั้งสองชุด "คูณ" เข้าด้วยกันจึงเป็น "ประเภทผลิตภัณฑ์"

ของ Haskell ประเภทพีชคณิตเป็นประเภทผลรวมของประเภทผลิตภัณฑ์ คุณให้ตัวสร้างหลายฟิลด์เพื่อสร้างชนิดผลิตภัณฑ์และคุณมีตัวสร้างหลายตัวเพื่อสร้างผลรวม (ของผลิตภัณฑ์)

ตัวอย่างเหตุผลที่อาจเป็นประโยชน์สมมติว่าคุณมีบางสิ่งที่ส่งออกข้อมูลเป็น XML หรือ JSON และต้องใช้ระเบียนการกำหนดค่า แต่เห็นได้ชัดว่าการตั้งค่าการกำหนดค่าสำหรับ XML และ JSON แตกต่างกันโดยสิ้นเชิง ดังนั้นคุณอาจทำสิ่งนี้:

data Config = XML_Config {...} | JSON_Config {...}

(เห็นได้ชัดว่ามีช่องที่เหมาะสมบางช่อง) คุณไม่สามารถทำสิ่งนี้ในภาษาโปรแกรมปกติได้ซึ่งเป็นสาเหตุที่คนส่วนใหญ่ไม่คุ้นเคยกับมัน


4
ที่ดี! เพียงสิ่งหนึ่ง "พวกเขาสามารถ ... ถูกสร้างขึ้นในเกือบทุกภาษา" วิกิพีเดียกล่าวว่า :) ในเช่น C / ++ นั่นคือunionด้วยระเบียบวินัยของแท็ก :)
Will Ness

5
ใช่ แต่เวลาที่ฉันพูดถึงทุกunionคนมองมาที่ฉันชอบ "ใครนรกที่เคยใช้ว่า ??" ;-)
MathematicalOrchid

1
ฉันได้เห็นการunionใช้งานมากมายในอาชีพ C ของฉัน โปรดอย่าทำให้มันฟังดูไม่จำเป็นเพราะนั่นไม่ใช่อย่างนั้น
truthadjustr

26

เริ่มจากกรณีที่ง่ายที่สุด:

data Color = Blue | Green | Red

กำหนดนี้เป็น "ประเภทคอนสตรัค" Colorซึ่งจะไม่มีข้อโต้แย้ง - และมันมีสาม "ข้อมูลการก่อสร้าง" Blue, และGreen Redตัวสร้างข้อมูลไม่มีข้อโต้แย้งใด ๆ ซึ่งหมายความว่ามีสามประเภทColor: Blue, และGreenRed

ตัวสร้างข้อมูลถูกใช้เมื่อคุณต้องการสร้างค่าของการจัดเรียงบางประเภท ชอบ:

myFavoriteColor :: Color
myFavoriteColor = Green

สร้างค่าmyFavoriteColorโดยใช้ตัวGreenสร้างข้อมูล - และmyFavoriteColorจะเป็นประเภทColorเนื่องจากเป็นประเภทของค่าที่สร้างโดยตัวสร้างข้อมูล

ตัวสร้างประเภทถูกใช้เมื่อคุณต้องการสร้างประเภทของการจัดเรียงบางประเภท โดยปกติจะเป็นกรณีนี้เมื่อเขียนลายเซ็น:

isFavoriteColor :: Color -> Bool

ในกรณีนี้คุณกำลังเรียกใช้Colortype constructor (ซึ่งไม่มีอาร์กิวเมนต์)

ยังอยู่กับฉันไหม

ทีนี้ลองนึกภาพคุณไม่เพียง แต่ต้องการสร้างค่าสีแดง / เขียว / น้ำเงิน แต่คุณยังต้องการระบุ "ความเข้ม" ด้วย เช่นเดียวกับค่าระหว่าง 0 ถึง 256 คุณสามารถทำได้โดยการเพิ่มอาร์กิวเมนต์ให้กับตัวสร้างข้อมูลแต่ละตัวดังนั้นคุณจึงลงเอยด้วย:

data Color = Blue Int | Green Int | Red Int

Intตอนนี้แต่ละสามก่อสร้างงานข้อมูลที่ใช้อาร์กิวเมนต์พิมพ์ type constructor ( Color) ยังคงไม่ใช้อาร์กิวเมนต์ใด ๆ ดังนั้นสีโปรดของฉันคือสีเขียวเข้มฉันสามารถเขียนได้

    myFavoriteColor :: Color
    myFavoriteColor = Green 50

และอีกครั้งก็เรียกตัวสร้างข้อมูลและฉันจะได้รับค่าของชนิดGreenColor

ลองนึกภาพว่าคุณไม่ต้องการกำหนดวิธีที่ผู้คนแสดงออกถึงความเข้มของสี บางคนอาจต้องการค่าตัวเลขเหมือนที่เราเพิ่งทำ คนอื่น ๆ อาจใช้ได้เพียงแค่บูลีนที่ระบุว่า "สว่าง" หรือ "ไม่สว่างมาก" วิธีแก้ปัญหานี้คือการไม่ฮาร์ดโค้ดIntในตัวสร้างข้อมูล แต่ให้ใช้ตัวแปรประเภท:

data Color a = Blue a | Green a | Red a

ตอนนี้ตัวสร้างประเภทของเราใช้อาร์กิวเมนต์หนึ่งรายการ (อีกประเภทหนึ่งซึ่งเราเรียกว่าa!) และตัวสร้างข้อมูลทั้งหมดจะใช้อาร์กิวเมนต์ (ค่า!) หนึ่งaรายการ ดังนั้นคุณสามารถมี

myFavoriteColor :: Color Bool
myFavoriteColor = Green False

หรือ

myFavoriteColor :: Color Int
myFavoriteColor = Green 50

สังเกตว่าเราเรียกตัวColorสร้างประเภทด้วยอาร์กิวเมนต์ (ประเภทอื่น) อย่างไรเพื่อให้ได้ประเภท "ประสิทธิผล" ซึ่งจะถูกส่งกลับโดยผู้สร้างข้อมูล สิ่งนี้สัมผัสถึงแนวคิดของประเภทที่คุณอาจต้องการอ่านเกี่ยวกับกาแฟหนึ่งหรือสองแก้ว

ตอนนี้เราได้ทราบแล้วว่าตัวสร้างข้อมูลและตัวสร้างประเภทคืออะไรและตัวสร้างข้อมูลสามารถรับค่าอื่น ๆ เป็นอาร์กิวเมนต์ได้อย่างไรและตัวสร้างประเภทสามารถใช้ประเภทอื่นเป็นอาร์กิวเมนต์ได้อย่างไร HTH


ฉันไม่แน่ใจว่าฉันเป็นเพื่อนกับความคิดของคุณเกี่ยวกับตัวสร้างข้อมูลว่างเปล่า ฉันรู้ว่าเป็นวิธีทั่วไปในการพูดคุยเกี่ยวกับค่าคงที่ใน Haskell แต่ไม่ได้รับการพิสูจน์ว่าไม่ถูกต้องสักสองสามครั้งหรือไม่?
kqr

@kqr: ตัวสร้างข้อมูลอาจเป็นโมฆะ แต่ก็ไม่ใช่ฟังก์ชันอีกต่อไป ฟังก์ชันคือสิ่งที่ใช้อาร์กิวเมนต์และให้ค่าคือบางสิ่งที่มี->อยู่ในลายเซ็น
Frerich Raabe

ค่าสามารถชี้ไปยังหลายประเภทได้หรือไม่? หรือทุกค่าเกี่ยวข้องกับ 1 ประเภทเท่านั้นและแค่นั้นเอง?
CMCDragonkai

1
@jrg มีการทับซ้อนกันบ้าง แต่ไม่เฉพาะเจาะจงเนื่องจากตัวสร้างประเภท แต่เป็นเพราะตัวแปรประเภทเช่นaในdata Color a = Red a. aเป็นตัวยึดสำหรับประเภทโดยพลการ คุณสามารถมีเหมือนกันได้ในฟังก์ชันธรรมดาเช่นฟังก์ชันประเภท(a, b) -> aรับค่าทูเพิลจากสองค่า (ประเภทaและb) และให้ค่าแรก เป็นฟังก์ชัน "ทั่วไป" ซึ่งไม่ได้กำหนดประเภทขององค์ประกอบทูเปิล - เพียงระบุว่าฟังก์ชันให้ค่าประเภทเดียวกับองค์ประกอบทูเปิลแรก
Frerich Raabe

1
+1 Now, our type constructor takes one argument (another type which we just call a!) and all of the data constructors will take one argument (a value!) of that type a.สิ่งนี้มีประโยชน์มาก
Jonas

5

ดังที่คนอื่น ๆ ชี้ให้เห็นความหลากหลายไม่ได้มีประโยชน์อย่างมากที่นี่ ลองดูตัวอย่างอื่นที่คุณอาจคุ้นเคยอยู่แล้ว:

Maybe a = Just a | Nothing

ประเภทนี้มีตัวสร้างข้อมูลสองตัว Nothingค่อนข้างน่าเบื่อไม่มีข้อมูลที่เป็นประโยชน์ บนมืออื่น ๆJustที่มีค่าของa- สิ่งที่ประเภทaอาจมี ลองเขียนฟังก์ชั่นที่ใช้ประเภทนี้เช่นรับส่วนหัวของIntรายการถ้ามี (ฉันหวังว่าคุณจะยอมรับว่านี่มีประโยชน์มากกว่าการโยนข้อผิดพลาด):

maybeHead :: [Int] -> Maybe Int
maybeHead [] = Nothing
maybeHead (x:_) = Just x

> maybeHead [1,2,3]    -- Just 1
> maybeHead []         -- None

ดังนั้นในกรณีนี้aคือ an Intแต่ก็ใช้ได้เช่นกันสำหรับประเภทอื่น ๆ ในความเป็นจริงคุณสามารถทำให้ฟังก์ชั่นของเราใช้งานได้กับรายการทุกประเภท (แม้ว่าจะไม่เปลี่ยนการใช้งานก็ตาม):

maybeHead :: [t] -> Maybe t
maybeHead [] = Nothing
maybeHead (x:_) = Just x

ในทางกลับกันคุณสามารถเขียนฟังก์ชันที่ยอมรับได้เฉพาะบางประเภทMaybeเช่น

doubleMaybe :: Maybe Int -> Maybe Int
doubleMaybe Just x = Just (2*x)
doubleMaybe Nothing= Nothing

เรื่องสั้นขนาดยาวด้วยความหลากหลายคุณให้ความยืดหยุ่นในการทำงานกับคุณค่าของประเภทอื่น ๆ

ในตัวอย่างของคุณคุณอาจตัดสินใจในบางจุดที่Stringไม่เพียงพอที่จะระบุ บริษัท แต่จำเป็นต้องมีประเภทของตัวเองCompany(ซึ่งมีข้อมูลเพิ่มเติมเช่นประเทศที่อยู่บัญชีหลัง ฯลฯ ) การใช้งานครั้งแรกของคุณCarจะต้องเปลี่ยนเป็นการใช้CompanyแทนStringค่าแรก การใช้งานครั้งที่สองของคุณใช้ได้ดีคุณใช้Car Company String Intงานได้เหมือนเดิม (แน่นอนว่าต้องมีการเปลี่ยนแปลงฟังก์ชันการเข้าถึงข้อมูล บริษัท )


คุณสามารถใช้ตัวสร้างชนิดในบริบทข้อมูลของการประกาศข้อมูลอื่นได้หรือไม่ บางอย่างเช่นdata Color = Blue ; data Bright = Color? ฉันลองใช้ ghci และดูเหมือนว่า Colour ใน type constructor ไม่มีส่วนเกี่ยวข้องกับตัวสร้างข้อมูลสีในนิยาม Bright มีตัวสร้างสีเพียง 2 ตัวคือตัวสร้างข้อมูลและอีกตัวคือประเภท
CMCDragonkai

@CMCDragonkai ฉันไม่คิดว่าคุณจะทำได้และฉันก็ไม่แน่ใจด้วยซ้ำว่าคุณต้องการบรรลุอะไรกับสิ่งนี้ คุณสามารถ "ตัด" ประเภทที่มีอยู่โดยใช้dataหรือnewtype(เช่นdata Bright = Bright Color) หรือคุณสามารถใช้typeเพื่อกำหนดคำพ้องความหมาย (เช่นtype Bright = Color)
Landei

5

อันที่สองมีแนวคิดของ "ความหลากหลาย" อยู่ในนั้น

a b cสามารถของชนิดใด ๆ ยกตัวอย่างเช่นaอาจจะเป็น[String], bสามารถ[Int] และสามารถc[Char]

ในขณะที่ประเภทคนแรกที่จะคงที่: บริษัท เป็นStringรูปแบบเป็นปีที่เป็นStringInt

ตัวอย่างรถยนต์อาจไม่แสดงความสำคัญของการใช้ความหลากหลาย แต่สมมติว่าข้อมูลของคุณอยู่ในประเภทรายการ รายการสามารถมีได้String, Char, Int ...ในสถานการณ์เหล่านั้นคุณจะต้องมีวิธีที่สองในการกำหนดข้อมูลของคุณ

สำหรับวิธีที่สามฉันไม่คิดว่ามันจะต้องเข้ากับประเภทก่อนหน้านี้ เป็นเพียงวิธีหนึ่งในการกำหนดข้อมูลใน Haskell

นี่เป็นความคิดเห็นที่ต่ำต้อยของฉันในฐานะผู้เริ่มต้นด้วยตัวเอง

Btw: ตรวจสอบให้แน่ใจว่าคุณฝึกสมองได้ดีและรู้สึกสบายใจกับสิ่งนี้ เป็นกุญแจสำคัญในการทำความเข้าใจโมนาดในภายหลัง


1

เป็นเรื่องเกี่ยวกับประเภท : ในกรณีแรกคุณกำหนดประเภทString(สำหรับ บริษัท และรุ่น) และIntสำหรับปี ในกรณีที่สองของคุณเป็นแบบทั่วไปมากกว่า a, bและcอาจจะเป็นชนิดเดียวกันมากเช่นในตัวอย่างแรกหรือบางสิ่งบางอย่างที่แตกต่างกันอย่างสิ้นเชิง เช่นอาจเป็นประโยชน์ในการกำหนดปีเป็นสตริงแทนจำนวนเต็ม และถ้าคุณต้องการคุณอาจใช้Colorประเภทของคุณก็ได้

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.