คำหลัก "forall" ใน Haskell / GHC ทำอะไรได้บ้าง


312

ฉันเริ่มเข้าใจวิธีการใช้forallคำหลักใน "ประเภทที่มีอยู่" เช่นนี้:

data ShowBox = forall s. Show s => SB s

อย่างไรก็ตามนี่เป็นเพียงส่วนย่อยของวิธีการforallใช้งานและฉันก็ไม่สามารถห่อหุ้มใจการใช้งานในสิ่งต่าง ๆ เช่นนี้:

runST :: forall a. (forall s. ST s a) -> a

หรืออธิบายว่าทำไมสิ่งเหล่านี้จึงแตกต่าง:

foo :: (forall a. a -> a) -> (Char, Bool)
bar :: forall a. ((a -> a) -> (Char, Bool))

หรือทั้งหมดRankNTypes...

ฉันมักจะชอบภาษาอังกฤษที่ชัดเจนและไร้คำศัพท์มากกว่าภาษาที่เป็นเรื่องปกติในสภาพแวดล้อมทางวิชาการ คำอธิบายส่วนใหญ่ที่ฉันพยายามอ่านเกี่ยวกับเรื่องนี้ (สิ่งที่ฉันสามารถหาได้จากเครื่องมือค้นหา) มีปัญหาเหล่านี้:

  1. มันไม่สมบูรณ์ พวกเขาอธิบายส่วนหนึ่งของการใช้คำหลักนี้ (เช่น "ประเภทอัตถิภาวนิยม") ซึ่งทำให้ผมรู้สึกมีความสุขจนกว่าฉันจะอ่านรหัสที่ใช้มันในทางที่แตกต่างกันอย่างสมบูรณ์ (เช่นrunST, fooและbarด้านบน)
  2. พวกเขาเต็มไปด้วยข้อสันนิษฐานที่ฉันได้อ่านล่าสุดในสาขาคณิตศาสตร์ไม่ต่อเนื่องทฤษฎีหมวดหมู่หรือพีชคณิตนามธรรมที่เป็นที่นิยมในสัปดาห์นี้ (ถ้าฉันไม่เคยอ่านคำว่า "อ่านบทความใด ๆสำหรับรายละเอียดของการใช้งาน" อีกครั้งมันจะเร็วเกินไป)
  3. มันถูกเขียนด้วยวิธีที่เปลี่ยนแนวความคิดที่เรียบง่ายให้กลายเป็นไวยากรณ์และความหมายที่บิดเบี้ยวและแตกหัก

ดังนั้น...

ตามคำถามจริง ใครสามารถอธิบายforallคำหลักในภาษาอังกฤษที่ชัดเจนและชัดเจน (หรือถ้ามีอยู่ที่ใดที่หนึ่งให้ชี้ไปที่คำอธิบายที่ชัดเจนซึ่งฉันพลาดไป) ซึ่งไม่คิดว่าฉันเป็นนักคณิตศาสตร์ที่แพร่หลายในศัพท์แสง?


แก้ไขเพื่อเพิ่ม:

มีคำตอบที่โดดเด่นสองคำจากคำตอบที่มีคุณภาพสูงกว่าด้านล่าง แต่น่าเสียดายที่ฉันสามารถเลือกคำตอบที่ดีที่สุดเท่านั้น คำตอบของนอร์มันนั้นมีรายละเอียดและมีประโยชน์อธิบายสิ่งต่าง ๆ ในแบบที่แสดงให้เห็นถึงการสนับสนุนทางทฤษฎีforallและในขณะเดียวกันก็แสดงให้ฉันเห็นถึงผลกระทบเชิงปฏิบัติบางประการของมัน คำตอบของ yairchuครอบคลุมพื้นที่ที่ไม่มีใครพูดถึง (ตัวแปรประเภทที่กำหนดขอบเขต) และแสดงแนวคิดทั้งหมดด้วยรหัสและเซสชัน GHCi เป็นไปได้หรือไม่ที่จะเลือกทั้งสองอย่างดีที่สุด น่าเสียดายที่ฉันทำไม่ได้และหลังจากดูคำตอบทั้งสองอย่างใกล้ชิดฉันตัดสินใจแล้วว่า yairchu นั้นเปรียบได้กับนอร์แมนเล็กน้อยเนื่องจากรหัสตัวอย่างและคำอธิบายที่แนบมา มันค่อนข้างไม่ยุติธรรม แต่เพราะฉันต้องการคำตอบทั้งสองเพื่อที่จะเข้าใจสิ่งนี้จนถึงจุดที่forallไม่ทำให้ฉันหวาดกลัวเมื่อฉันเห็นมันในลายเซ็นประเภท


7
Hiki Hikiดูเหมือนจะเป็นมิตรกับผู้เริ่มต้นในหัวข้อนี้
jhegedus

คำตอบ:


263

เริ่มด้วยตัวอย่างรหัส:

foob :: forall a b. (b -> b) -> b -> (a -> b) -> Maybe a -> b
foob postProcess onNothin onJust mval =
    postProcess val
    where
        val :: b
        val = maybe onNothin onJust mval

รหัสนี้ไม่ได้รวบรวม (ข้อผิดพลาดทางไวยากรณ์) ใน Haskell 98 ธรรมดามันต้องมีส่วนขยายเพื่อรองรับforallคำหลัก

โดยทั่วไปมี 3 ที่แตกต่างกันการใช้งานทั่วไปสำหรับforallคำหลัก (หรืออย่างน้อยดังนั้นจึงดูเหมือนว่า ) และแต่ละคนมีการขยายตัวของมันเอง Haskell: ScopedTypeVariables, RankNTypes/ ,Rank2TypesExistentialQuantification

รหัสด้านบนไม่ได้รับข้อผิดพลาดทางไวยากรณ์เมื่อเปิดใช้งานอย่างใดอย่างหนึ่ง แต่ตรวจสอบเฉพาะประเภทที่ScopedTypeVariablesเปิดใช้งาน

ตัวแปรประเภทที่กำหนดขอบเขต:

ตัวแปรชนิดที่กำหนดขอบเขตช่วยหนึ่งระบุชนิดสำหรับโค้ดภายในส่วนwhereคำสั่ง มันทำให้bในval :: bหนึ่งเดียวกับในbfoob :: forall a b. (b -> b) -> b -> (a -> b) -> Maybe a -> b

จุดที่ทำให้สับสน : คุณอาจได้ยินว่าเมื่อคุณละเว้นforallจากประเภทมันจะยังคงอยู่ที่นั่นโดยปริยาย ( จากคำตอบของนอร์แมน: "โดยปกติภาษาเหล่านี้จะไม่ใช้ฟอร์แมทจาก polymorphic types" ) การอ้างสิทธิ์นี้ถูกต้องแต่หมายถึงการใช้อื่น ๆ ของforallและไม่ใช่การScopedTypeVariablesใช้งาน

อันดับ-N-ประเภท:

ขอเริ่มต้นด้วยที่mayb :: b -> (a -> b) -> Maybe a -> bเทียบเท่ากับmayb :: forall a b. b -> (a -> b) -> Maybe a -> b, ยกเว้นเมื่อScopedTypeVariablesมีการใช้งาน

ซึ่งหมายความว่าการทำงานทุกและab

สมมติว่าคุณต้องการทำอะไรแบบนี้

ghci> let putInList x = [x]
ghci> liftTup putInList (5, "Blah")
([5], ["Blah"])

สิ่งนี้จะต้องเป็นประเภทliftTupใด? liftTup :: (forall x. x -> f x) -> (a, b) -> (f a, f b)มัน เพื่อดูว่าทำไมลองรหัสมัน:

ghci> let liftTup liftFunc (a, b) = (liftFunc a, liftFunc b)
ghci> liftTup (\x -> [x]) (5, "Hello")
    No instance for (Num [Char])
    ...
ghci> -- huh?
ghci> :t liftTup
liftTup :: (t -> t1) -> (t, t) -> (t1, t1)

"อืม .. ทำไม GHC ถึงอนุมานได้ว่าทูเปิลต้องมีประเภทเดียวกันสองประเภทลองบอกกันว่าพวกมันไม่จำเป็นต้องเป็น"

-- test.hs
liftTup :: (x -> f x) -> (a, b) -> (f a, f b)
liftTup liftFunc (t, v) = (liftFunc t, liftFunc v)

ghci> :l test.hs
    Couldnt match expected type 'x' against inferred type 'b'
    ...

อืมมม ดังนั้นที่นี่ GHC ไม่ให้เราใช้liftFuncในvเพราะv :: bและliftFuncต้องการ xเราต้องการให้ฟังก์ชันของเรารับฟังก์ชั่นที่ยอมรับได้ทุก ๆ อย่างx!

{-# LANGUAGE RankNTypes #-}
liftTup :: (forall x. x -> f x) -> (a, b) -> (f a, f b)
liftTup liftFunc (t, v) = (liftFunc t, liftFunc v)

ดังนั้นมันจึงไม่liftTupเหมาะกับทุกคนxมันเป็นฟังก์ชั่นที่ได้รับ

ปริมาณที่มีอยู่:

ลองใช้ตัวอย่าง:

-- test.hs
{-# LANGUAGE ExistentialQuantification #-}
data EQList = forall a. EQList [a]
eqListLen :: EQList -> Int
eqListLen (EQList x) = length x

ghci> :l test.hs
ghci> eqListLen $ EQList ["Hello", "World"]
2

มันแตกต่างจาก Rank-N-Types อย่างไร

ghci> :set -XRankNTypes
ghci> length (["Hello", "World"] :: forall a. [a])
    Couldnt match expected type 'a' against inferred type '[Char]'
    ...

มียศ-N-ประเภทforall aนั่นหมายความว่าการแสดงออกของคุณจะต้องพอดีเป็นไปได้ทั้งหมดas ตัวอย่างเช่น:

ghci> length ([] :: forall a. [a])
0

รายการว่างจะทำงานเป็นรายการประเภทใดก็ได้

ดังนั้นด้วยอัตถิภาวนิยม-ปริมาณ, forallในdataคำจำกัดความหมายความว่าค่าที่มีอยู่สามารถเป็นที่ใด ๆประเภทที่เหมาะสมไม่ว่ามันจะต้องเป็นของทุกประเภทที่เหมาะสม


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

2
จริงๆแล้วคุณทำให้ScopedTypeVariablesดูแย่กว่าที่เป็นอยู่ ถ้าคุณเขียนประเภทที่มีนามสกุลนี้มันจะยังคงเป็นว่าเทียบเท่ากับb -> (a -> b) -> Maybe a -> b forall a b. b -> (a -> b) -> Maybe a -> bแต่ถ้าคุณต้องการที่จะอ้างถึงเดียวกัน b (และไม่ได้วัดโดยปริยาย) แล้วคุณจะต้องเขียนรุ่นเชิงปริมาณอย่างชัดเจน มิฉะนั้นSTVจะเป็นส่วนขยายที่ล่วงล้ำอย่างมาก
nominolo

1
@nominolo: ฉันไม่ได้ตั้งใจจะดูถูกScopedTypeVariablesและฉันไม่คิดว่ามันจะไม่ดี imho มันเป็นเครื่องมือที่มีประโยชน์มากสำหรับกระบวนการเขียนโปรแกรมและโดยเฉพาะอย่างยิ่งสำหรับผู้เริ่มต้น Haskell และฉันรู้สึกขอบคุณที่มีอยู่
yairchu

2
นี่เป็นคำถามที่ค่อนข้างเก่า (และคำตอบ) แต่มันอาจคุ้มค่าที่จะอัปเดตเพื่อสะท้อนความจริงที่ว่าประเภทที่มีอยู่สามารถแสดงได้โดยใช้ GADT ในแบบที่ (กับฉันอย่างน้อย) ทำให้การหาปริมาณง่ายขึ้นมาก
dfeuer

1
โดยส่วนตัวฉันคิดว่าเป็นการง่ายที่จะอธิบาย / เข้าใจสัญลักษณ์ที่มีอยู่ในแง่ของการแปลไปยังแบบฟอร์ม GADT มากกว่าด้วยตนเอง แต่คุณมีอิสระที่จะคิดอย่างอื่น
dfeuer

117

ใครสามารถสมบูรณ์อธิบายคำหลัก forall ที่ชัดเจนและเป็นภาษาอังกฤษธรรมดา?

ไม่ (อาจจะเป็น Don Stewart ก็ได้)

นี่คืออุปสรรคในการอธิบายอย่างง่ายชัดเจนหรือforall:

  • มันเป็นปริมาณ คุณต้องมีอย่างน้อยตรรกะน้อย (แคลคูลัสเพรดิเคต) เพื่อที่จะได้เห็นตัวระบุที่เป็นสากลหรือที่มีอยู่ ถ้าคุณไม่เคยเห็นแคลคูลัสหรือไม่พอใจกับปริมาณ (และฉันได้เห็นนักเรียนในช่วงปริญญาเอกการสอบที่มีความไม่สบายที่มีคุณสมบัติเหมาะสม) forallแล้วสำหรับคุณไม่มีคำอธิบายที่ง่าย

  • มันเป็นประเภทปริมาณ หากคุณไม่ได้เห็นSystem Fและได้รับแบบฝึกหัดการเขียน polymorphic บางประเภทคุณจะรู้สึกforallสับสน ประสบการณ์กับ Haskell หรือ ML นั้นไม่เพียงพอเพราะปกติแล้วภาษาเหล่านี้จะไม่ใช้forallโพลีมอร์ฟิค (ในใจของฉันนี่คือความผิดพลาดในการออกแบบภาษา)

  • โดยเฉพาะอย่างยิ่งใน Haskell forallมีการใช้ในรูปแบบที่ทำให้ฉันสับสน (ฉันไม่ใช่นักทฤษฎีประเภท แต่งานของฉันทำให้ฉันติดต่อกับทฤษฎีประเภทจำนวนมากและฉันค่อนข้างสบายใจกับมัน) สำหรับฉันแหล่งที่มาหลักของความสับสนforallคือใช้ในการเข้ารหัสประเภทที่ existsตัวผมเองต้องการที่จะเขียนด้วย มันเป็นธรรมจากมอร์ฟิซึ่มส์ประเภทหนึ่งที่ซับซ้อนซึ่งเกี่ยวกับควอนตัมและลูกศรและทุกครั้งที่ฉันต้องการที่จะเข้าใจมันฉันต้องมองหาสิ่งต่างๆ

    หากคุณไม่สะดวกกับแนวคิดของมอร์ฟิซึ่มประเภทหรือถ้าคุณไม่มีความคิดใด ๆ เกี่ยวกับมอร์ฟประเภทนี้การใช้งานforallนี้จะทำให้คุณเบื่อ

  • ในขณะที่แนวคิดทั่วไปของforallจะเหมือนเดิมเสมอ (การผูกเพื่อแนะนำตัวแปรชนิด) รายละเอียดของการใช้ที่แตกต่างกันอาจแตกต่างกันอย่างมีนัยสำคัญ ภาษาอังกฤษนอกระบบนั้นไม่ใช่เครื่องมือที่ดีมากในการอธิบายรูปแบบต่าง ๆ เพื่อให้เข้าใจว่าเกิดอะไรขึ้นคุณต้องมีคณิตศาสตร์ ในกรณีนี้คณิตศาสตร์ที่เกี่ยวข้องสามารถพบได้ในประเภทข้อความเบื้องต้นและภาษาโปรแกรมของเบนจามินเพียร์ซซึ่งเป็นหนังสือที่ดีมาก

สำหรับตัวอย่างเฉพาะของคุณ

  • runST ควรทำให้คุณปวดหัว ประเภทที่มีอันดับสูงกว่า (ไปทางซ้ายของลูกศร) จะไม่ค่อยพบในป่า ผมแนะนำให้คุณอ่านกระดาษที่แนะนำrunST: "ขี้เกียจทำงานรัฐเพิ่มเติม" นี่เป็นบทความที่ดีมากและมันจะทำให้คุณมีสัญชาตญาณที่ดีขึ้นสำหรับประเภทของrunSTเฉพาะและสำหรับประเภทที่มีอันดับสูงกว่าโดยทั่วไป คำอธิบายมีหลายหน้ามันทำได้ดีมากและฉันจะไม่พยายามย่อที่นี่

  • พิจารณา

    foo :: (forall a. a -> a) -> (Char,Bool)
    bar :: forall a. ((a -> a) -> (Char, Bool))

    ถ้าฉันโทรbarฉันสามารถเลือกประเภทaที่ฉันชอบและฉันสามารถส่งผ่านฟังก์ชั่นจากประเภทหนึ่งaไปยังอีกประเภทaหนึ่ง ตัวอย่างเช่นผมสามารถผ่านฟังก์ชั่นหรือฟังก์ชั่น(+1) reverseคุณสามารถคิดถึงforallคำพูดที่ว่า "ฉันจะเลือกประเภทตอนนี้" (คำศัพท์ทางเทคนิคสำหรับการเลือกประเภทกำลังเร่งความเร็ว)

    ข้อ จำกัด ในการโทรfooมีความเข้มงวดมากขึ้น: อาร์กิวเมนต์foo ต้องเป็นฟังก์ชัน polymorphic กับประเภทที่ฟังก์ชั่นเดียวที่ฉันสามารถส่งผ่านไปfooมีหรือฟังก์ชั่นที่มักจะลู่หรือข้อผิดพลาดเช่นid undefinedเหตุผลก็คือว่ามีfooความforallเป็นไปทางซ้ายของลูกศรเพื่อที่จะโทรของfooฉันไม่ได้รับเลือกสิ่งที่aเป็นค่อนข้างจะเป็นการดำเนินงานของfooที่ได้รับเลือกสิ่งที่aเป็น เนื่องจากforallอยู่ทางด้านซ้ายของลูกศรแทนที่จะอยู่เหนือลูกศรเช่นเดียวกับbarการสร้างอินสแตนซ์ในส่วนของฟังก์ชั่นแทนที่จะอยู่ที่ไซต์การโทร

สรุป: สมบูรณ์คำอธิบายของforallคำหลักที่ต้องใช้คณิตศาสตร์และสามารถเข้าใจได้โดยคนที่ได้ศึกษาคณิตศาสตร์เท่านั้น แม้แต่คำอธิบายบางส่วนก็ยากที่จะเข้าใจหากไม่มีคณิตศาสตร์ แต่บางทีคำอธิบายที่ไม่ใช่คณิตศาสตร์ของฉันอาจช่วยได้นิดหน่อย ไปอ่าน Launchbury และ Peyton Jones บนrunST!


ภาคผนวก:ศัพท์แสง "เหนือ", "ใต้", "ทางด้านซ้ายของ" สิ่งเหล่านี้ไม่มีส่วนเกี่ยวข้องกับชนิดของข้อความที่ถูกเขียนและทุกอย่างเกี่ยวกับต้นไม้ที่เป็นนามธรรม ในรูปแบบนามธรรมforallใช้ชื่อของตัวแปรชนิดแล้วมีชนิดเต็ม "ด้านล่าง" forall ลูกศรมีสองประเภท (ประเภทอาร์กิวเมนต์และประเภทผลลัพธ์) และรูปแบบใหม่ (ประเภทฟังก์ชั่น) ประเภทอาร์กิวเมนต์คือ "ทางซ้ายของ" ลูกศร มันเป็นลูกศรซ้ายของลูกศรในแผนผังต้นไม้แบบนามธรรม

ตัวอย่าง:

  • ในforall a . [a] -> [a]forall อยู่เหนือลูกศร; ด้านซ้ายของลูกศรคือ[a]อะไร

  • ใน

    forall n f e x . (forall e x . n e x -> f -> Fact x f) 
                  -> Block n e x -> f -> Fact x f

    ประเภทในวงเล็บจะถูกเรียกว่า "a forall ทางซ้ายของลูกศร" (ฉันใช้ประเภทเช่นนี้ในเครื่องมือเพิ่มประสิทธิภาพที่ฉันกำลังทำงานอยู่)


ที่จริงฉันได้ด้านบน / ล่าง / ด้านซ้ายโดยไม่ต้องคิด ฉันเป็นคนงี่เง่าใช่ แต่คนโง่ที่เคยต่อสู้กับสิ่งนั้นมาก่อน (การเขียนคอมไพเลอร์ ASN.1 ท่ามกลางคนอื่น ๆ ;) ขอบคุณสำหรับภาคผนวก
เพียงความคิดเห็นที่ถูกต้องของฉัน

12
@ เพียงแค่ขอบคุณ แต่ฉันเขียนเพื่อลูกหลาน ฉันพบโปรแกรมเมอร์มากกว่าหนึ่งคนที่คิดว่าข้างในforall a . [a] -> [a]นั้นทางด้านซ้ายของลูกศร
นอร์แมนแรมซีย์

ตกลงไปดูรายละเอียดคำตอบของคุณตอนนี้ฉันต้องขอบคุณนอร์แมนจากหัวใจของฉัน มีหลายสิ่งที่เข้ามาเกี่ยวข้องกับการคลิกที่ดังในขณะนี้และสิ่งที่ฉันยังไม่เข้าใจฉันอย่างน้อยก็รับรู้ว่าฉันไม่ได้ตั้งใจที่จะเข้าใจมันและจะผ่านforallในสถานการณ์เหล่านั้นอย่างมีประสิทธิภาพ สัญญาณรบกวน ฉันจะตรวจสอบกระดาษที่คุณเชื่อมโยงกับ (ขอบคุณสำหรับลิงค์เช่นกัน!) และดูว่ามันอยู่ในขอบเขตของความเข้าใจของฉันหรือไม่ ความรุ่งโรจน์
เพียงความคิดเห็นที่ถูกต้องของฉัน

10
ฉันอ่านไปทางซ้ายแล้วก็มองไปทางซ้ายอย่างแท้จริง ดังนั้นจึงไม่มีความชัดเจนสูงสำหรับฉันจนกว่าคุณจะพูดว่า "แยกต้นไม้"
พอลนาธาน

ขอบคุณที่ตัวชี้ไปที่หนังสือของเพียร์ซ มันมีคำอธิบายที่ชัดเจนมากเกี่ยวกับ System F มันอธิบายได้ว่าทำไมexistsไม่เคยถูกนำไปใช้ (ไม่ใช่ส่วนหนึ่งของ System F!) ใน Haskell ส่วนหนึ่งของ System F ถูกสร้างขึ้นมาโดยปริยาย แต่forallเป็นหนึ่งในสิ่งที่ไม่สามารถกวาดใต้พรมได้อย่างสมบูรณ์ ราวกับว่าพวกเขาเริ่มต้นด้วย Hindley-Milner ซึ่งจะอนุญาตให้forallมีการบอกเป็นนัย ๆ และเลือกใช้ระบบที่ทรงพลังยิ่งกว่าทำให้สับสนพวกเราที่ศึกษา 'forall' และ 'มีอยู่' ของ FOL และหยุดอยู่ที่นั่น
T_S_

50

คำตอบเดิมของฉัน:

ทุกคนสามารถอธิบายคำหลักทั้งหมดในภาษาอังกฤษที่ชัดเจนและชัดเจนได้หรือไม่

ดังที่นอร์แมนระบุว่าเป็นการยากที่จะให้คำอธิบายภาษาอังกฤษที่ชัดเจนและชัดเจนเกี่ยวกับศัพท์เทคนิคจากทฤษฎีประเภท เราทุกคนพยายาม

มีเพียงสิ่งเดียวที่มันจะจำเกี่ยวกับ 'forall' คือมันผูกประเภทขอบเขตบาง เมื่อคุณเข้าใจแล้วทุกอย่างค่อนข้างง่าย มันก็เหมือนกับของ 'แลมบ์ดา' (หรือรูปแบบของการ 'ให้' ก) ในระดับชนิด - นอร์แมนแรมซีย์ใช้ความคิดของ "ซ้าย" / การ "ด้านบน" เพื่อถ่ายทอดแนวคิดเดียวกันของขอบเขตนี้ในคำตอบที่ยอดเยี่ยมของเขา

การใช้งานส่วนใหญ่ของ 'forall' นั้นง่ายมากและคุณสามารถค้นหาได้ในคู่มือผู้ใช้ GHC, S7.8 . โดยเฉพาะอย่างยิ่งS7.8.5 ที่ยอดเยี่ยมในรูปแบบซ้อนของ 'forall'

ใน Haskell เรามักจะออกจากสารยึดเกาะสำหรับประเภทเมื่อประเภทเป็น quanitified ในระดับสากลเช่น:

length :: forall a. [a] -> Int

เทียบเท่ากับ:

length :: [a] -> Int

แค่นั้นแหละ.

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

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


4
ในทางเทคนิคlength :: forall a. [a] -> Intไม่เท่ากับlength :: [a] -> Intเมื่อScopedTypeVariablesเปิดใช้งาน เมื่อforall a.อยู่ที่นั่นมันมีผลกระทบlengthของwhereข้อ (ถ้ามีหนึ่ง) และการเปลี่ยนแปลงความหมายของตัวแปรประเภทชื่อaอยู่ในนั้น
yairchu

2
จริง ScopedTypeVariables ทำให้เรื่องราวซับซ้อนขึ้นเล็กน้อย
Don Stewart

3
@ DonStewart, อาจ "มันผูกประเภทกับขอบเขตบางส่วน" ดีกว่า phrased เป็น "มันผูกตัวแปรประเภทกับขอบเขตบางส่วน" ในคำอธิบายของคุณ?
Romildo

31

พวกเขาเต็มไปด้วยสมมติฐานที่ฉันอ่านล่าสุดในสาขาคณิตศาสตร์ไม่ต่อเนื่องทฤษฎีหมวดหมู่หรือพีชคณิตนามธรรมที่เป็นที่นิยมในสัปดาห์นี้ (ถ้าฉันไม่เคยอ่านคำว่า "อ่านบทความใด ๆ สำหรับรายละเอียดของการใช้งาน" อีกครั้งมันจะเร็วเกินไป)

เอ้อและตรรกะเกี่ยวกับการสั่งซื้อครั้งแรกง่ายอะไร forallค่อนข้างชัดเจนในการอ้างถึงปริมาณสากลและในบริบทนั้นคำว่าอัตถิภาวนิยมก็สมเหตุสมผลเช่นกันแม้ว่ามันจะน่าอึดอัดใจน้อยกว่าหากมีexistsคำหลัก ไม่ว่าปริมาณจะเป็นสากลหรือมีอยู่ได้อย่างมีประสิทธิภาพขึ้นอยู่กับการวางของปริมาณที่เกี่ยวข้องกับการที่ตัวแปรจะถูกใช้ในด้านของลูกศรฟังก์ชั่นและมันทั้งหมดสับสนเล็กน้อย

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

ดังนั้นพิจารณาidการทำงาน:

id :: forall a. a -> a
id x = x

เราสามารถเขียนมันใหม่เป็น lambdas โดยย้าย "พารามิเตอร์ประเภท" ออกจากประเภทลายเซ็นและเพิ่มคำอธิบายประกอบประเภท inline:

id = /\a -> (\x -> x) :: a -> a

นี่เป็นสิ่งเดียวกันกับconst:

const = /\a b -> (\x y -> x) :: a -> b -> a

ดังนั้นbarฟังก์ชันของคุณอาจเป็นดังนี้:

bar = /\a -> (\f -> ('t', True)) :: (a -> a) -> (Char, Bool)

โปรดทราบว่าประเภทของฟังก์ชันที่กำหนดให้barเป็นอาร์กิวเมนต์จะขึ้นอยู่กับbarพารามิเตอร์ประเภทของ พิจารณาว่าคุณมีอะไรแบบนี้แทน:

bar2 = /\a -> (\f -> (f 't', True)) :: (a -> a) -> (Char, Bool)

นี่bar2คือการใช้ฟังก์ชั่นบางอย่างของประเภทCharดังนั้นการให้bar2พารามิเตอร์ประเภทใด ๆ นอกเหนือจากCharจะทำให้เกิดข้อผิดพลาดประเภท

ในทางตรงกันข้ามนี่คือสิ่งที่fooอาจมีลักษณะ:

foo = (\f -> (f Char 't', f Bool True))

ซึ่งแตกต่างbar, fooไม่จริงใช้พารามิเตอร์ชนิดใด ๆ เลย! มันใช้ฟังก์ชั่นที่ตัวเองใช้พารามิเตอร์ประเภทจากนั้นใช้ฟังก์ชั่นนั้นกับสองประเภทที่แตกต่างกัน

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


โพสต์สคริปต์ : บางทีคุณอาจสงสัย - ตอนนี้เรากำลังคิดเกี่ยวกับฟังก์ชั่นที่ใช้พารามิเตอร์ประเภททำไมเราไม่ทำสิ่งที่น่าสนใจกับพารามิเตอร์เหล่านั้นมากกว่าที่จะใส่มันลงในลายเซ็นประเภท? คำตอบคือเราทำได้!

ฟังก์ชั่นที่ใส่ตัวแปรประเภทพร้อมกับป้ายชื่อและส่งคืนชนิดใหม่คือตัวสร้างประเภทซึ่งคุณสามารถเขียนสิ่งนี้:

Either = /\a b -> ...

แต่เราจำเป็นต้องมีสัญกรณ์ใหม่อย่างสมบูรณ์เพราะวิธีการเขียนประเภทเช่นEither a bนี้มีการชี้นำแล้วว่า "ใช้ฟังก์ชันEitherกับพารามิเตอร์เหล่านี้"

บนมืออื่น ๆ , ฟังก์ชั่นที่จัดเรียงของ "รูปแบบแมตช์" กับพารามิเตอร์ชนิดกลับค่าที่แตกต่างกันสำหรับประเภทที่แตกต่างกันเป็นวิธีการของการเรียนประเภท การขยายเล็กน้อยไปยัง/\ไวยากรณ์ของฉันด้านบนแสดงให้เห็นดังนี้:

fmap = /\ f a b -> case f of
    Maybe -> (\g x -> case x of
        Just y -> Just b g y
        Nothing -> Nothing b) :: (a -> b) -> Maybe a -> Maybe b
    [] -> (\g x -> case x of
        (y:ys) -> g y : fmap [] a b g ys 
        []     -> [] b) :: (a -> b) -> [a] -> [b]

โดยส่วนตัวฉันคิดว่าฉันชอบไวยากรณ์ที่แท้จริงของ Haskell ...

ฟังก์ชั่นที่ "รูปแบบจับคู่" พารามิเตอร์ประเภทของมันและส่งกลับโดยพลการชนิดที่มีอยู่คือประเภทครอบครัวหรือการพึ่งพาการทำงาน - ในกรณีที่ผ่านมามันก็ดูมากเช่นนิยามฟังก์ชั่น


1
สิ่งที่น่าสนใจที่นี่ นี่เป็นอีกมุมมองหนึ่งของการโจมตีปัญหาที่สามารถพิสูจน์ได้ว่ามีผลในระยะยาว ขอบคุณ
เพียงความคิดเห็นที่ถูกต้องของฉัน

@ KennyTM: หรือλสำหรับเรื่องนั้น แต่ส่วนขยายไวยากรณ์ unicode ของ GHC ไม่สนับสนุนสิ่งนั้นเพราะλเป็นจดหมายความจริงที่โชคร้ายที่สมมุติขึ้นมาจะนำไปใช้กับ abstract-lambda ที่เป็นสมมุติได้ ดังนั้นโดยเปรียบเทียบกับ/\ \ ฉันคิดว่าฉันสามารถใช้แต่ฉันพยายามหลีกเลี่ยงการคำนวณแคลคูลัส ...
CA McCann

29

นี่คือคำอธิบายที่รวดเร็วและสกปรกในแง่ธรรมดาที่คุณน่าจะคุ้นเคยอยู่แล้ว

forallคำหลักที่เป็นจริงที่ใช้เฉพาะในทางเดียวใน Haskell มันหมายถึงสิ่งเดียวกันเสมอเมื่อคุณเห็นมัน

ปริมาณสากล

ประเภทวัดสากลforall a. f aคือประเภทของรูปแบบ ค่าของชนิดที่สามารถจะคิดว่าเป็นฟังก์ชั่นที่ใช้ประเภท aเป็นอาร์กิวเมนต์และส่งกลับค่าf aจากประเภท ยกเว้นว่าใน Haskell อาร์กิวเมนต์ประเภทนี้จะถูกส่งผ่านโดยระบบประเภท นี้ "ฟังก์ชั่น" มีการให้ค่าเดียวกันไม่ว่าชนิดที่ได้รับเพื่อให้มีค่าเป็นpolymorphic

forall a. [a]ตัวอย่างเช่นพิจารณาประเภท ค่าประเภทนั้นจะเป็นประเภทอื่นaและให้รายชื่อองค์ประกอบประเภทเดียวกันaกลับมา แน่นอนว่ามีการนำไปใช้งานเพียงอย่างเดียวเท่านั้น มันจะต้องให้รายการที่ว่างเปล่ากับคุณเพราะaอาจเป็นประเภทใดก็ได้ รายการที่ว่างเปล่าเป็นค่ารายการเดียวที่เป็น polymorphic ในประเภทองค์ประกอบ (เนื่องจากไม่มีองค์ประกอบ)

forall a. a -> aหรือประเภท โทรของฟังก์ชั่นดังกล่าวมีทั้งชนิดและค่าของชนิดa aการใช้งานนั้นจะต้องส่งคืนค่าชนิดเดียวกันaนั้น มีการนำไปใช้เพียงครั้งเดียวที่เป็นไปได้อีกครั้ง มันจะต้องส่งกลับค่าเดียวกับที่ได้รับ

ปริมาณที่มีอยู่

ประเภทปริมาณที่มีอยู่จะมีรูปแบบexists a. f aถ้า Haskell รองรับรูปแบบนั้น ค่าของชนิดที่สามารถจะคิดว่าเป็นคู่ (หรือ "ผลิตภัณฑ์") ซึ่งประกอบด้วยชนิดและค่าของชนิดaf a

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

ตกลงดังนั้นรอสักครู่ ทำไมแฮสเค็ลล์ใช้forallเพื่อแสดงประเภท "มีอยู่" ดังต่อไปนี้

data ShowBox = forall s. Show s => SB s

มันอาจสร้างความสับสน แต่มันอธิบายประเภทของตัวสร้างข้อมูล SB :

SB :: forall s. Show s => s -> ShowBox

เมื่อสร้างแล้วคุณสามารถนึกถึงคุณค่าของประเภทShowBoxที่ประกอบด้วยสองสิ่ง มันเป็นประเภทร่วมกับค่าของชนิดs sกล่าวอีกนัยหนึ่งมันเป็นค่าของประเภทปริมาณที่มีอยู่จริง ShowBoxสามารถเขียนได้จริง ๆexists s. Show s => sถ้า Haskell สนับสนุนสัญลักษณ์นั้น

runST และเพื่อน ๆ

ระบุว่าสิ่งเหล่านี้แตกต่างกันอย่างไร

foo :: (forall a. a -> a) -> (Char,Bool)
bar :: forall a. ((a -> a) -> (Char, Bool))

มาก่อนbarกัน มันต้องใช้เวลาประเภทaและหน้าที่ของชนิดและผลิตตามตัวอักษรประเภทa -> a (Char, Bool)เราสามารถเลือกIntเป็นaและให้ฟังก์ชั่นของประเภทInt -> Intเช่น แต่fooต่างกัน มันต้องการให้การใช้งานของfooสามารถผ่านประเภทใด ๆ ที่มันต้องการฟังก์ชั่นที่เราให้ idดังนั้นฟังก์ชั่นเดียวที่เราสมควรจะให้มันเป็น

ตอนนี้เราน่าจะสามารถจัดการกับความหมายของประเภทrunST:

runST :: forall a. (forall s. ST s a) -> a

ดังนั้นrunSTจะต้องสามารถสร้างคุณค่าของประเภทaไม่ว่าเราจะให้ประเภทaใด การทำเช่นนั้นจะใช้ข้อโต้แย้งของพิมพ์ซึ่งแน่นอนอย่างใดต้องผลิตforall s. ST s a aมีอะไรเพิ่มเติมก็จะต้องสามารถที่จะสร้างค่าของชนิดaไม่ว่าสิ่งที่พิมพ์การดำเนินการของการตัดสินใจที่จะให้เป็นrunSTs

ตกลงอะไร ประโยชน์คือสิ่งนี้ทำให้ข้อ จำกัด ในการโทรของrunSTในประเภทที่aไม่เกี่ยวข้องกับประเภทsเลย คุณไม่สามารถส่งค่าเป็นประเภทST s [s]ได้ อะไรที่ว่าในทางปฏิบัติก็คือการดำเนินการเป็นอิสระในการดำเนินการกลายพันธุ์ที่มีค่าประเภทrunST ชนิดที่รับประกันได้ว่าการกลายพันธุ์นี้อยู่ในท้องถิ่นเพื่อให้การดำเนินงานของsrunST

ชนิดของrunSTเป็นตัวอย่างของpolymorphic rank-2เนื่องจากชนิดของอาร์กิวเมนต์ประกอบด้วยตัวforallระบุ ประเภทfooข้างต้นเป็นของอันดับ 2 ประเภท polymorphic สามัญเช่นนั้นของbarคืออันดับ 1 แต่จะกลายเป็นอันดับ 2 หากประเภทของการขัดแย้งจะต้อง polymorphic ด้วยforallปริมาณของตัวเอง และถ้าฟังก์ชั่นรับอาร์กิวเมนต์อันดับ 2 ประเภทของมันคืออันดับ 3 และอื่น ๆ โดยทั่วไปประเภทที่เกิดการขัดแย้ง polymorphic ของตำแหน่งมียศnn + 1


11

ใครสามารถอธิบายคำสำคัญทั้งหมดในภาษาอังกฤษที่ชัดเจนและชัดเจน (หรือถ้ามีอยู่ที่ใดที่หนึ่งให้ชี้ไปที่คำอธิบายที่ชัดเจนซึ่งฉันพลาดไป) ซึ่งไม่คิดว่าฉันเป็นนักคณิตศาสตร์ที่แพร่หลายในศัพท์แสง?

ฉันจะพยายามอธิบายความหมายและการประยุกต์ใช้forallในบริบทของ Haskell และระบบการพิมพ์

แต่ก่อนที่คุณจะเข้าใจว่าฉันอยากจะนำคุณไปสู่การพูดคุยที่ดีและเข้าถึงได้ง่ายจาก Runar Bjarnason ในหัวข้อ " Constraints Liberate, Liberties Constrain " พูดคุยเต็มไปด้วยตัวอย่างจากกรณีการใช้งานจริงของโลกเช่นเดียวกับตัวอย่างใน Scala forallเพื่อสนับสนุนคำสั่งนี้แม้ว่ามันจะไม่ได้พูดถึง ฉันจะพยายามอธิบายforallมุมมองด้านล่าง

                CONSTRAINTS LIBERATE, LIBERTIES CONSTRAIN

มันสำคัญมากที่จะแยกแยะและเชื่อว่าคำสั่งนี้เพื่อดำเนินการตามคำอธิบายต่อไปนี้ดังนั้นฉันขอแนะนำให้คุณดูการพูดคุย (อย่างน้อยก็บางส่วน)

ตอนนี้เป็นตัวอย่างที่พบบ่อยมากการแสดงความหมายของระบบประเภท Haskell เป็นลายเซ็นประเภทนี้:

foo :: a -> a

มันก็บอกว่าได้รับลายเซ็นประเภทนี้มีเพียงหนึ่งฟังก์ชั่นที่สามารถตอบสนองประเภทนี้และที่เป็นฟังก์ชั่นหรือสิ่งที่เป็นที่รู้จักกันอย่างแพร่หลายมากขึ้นidentityid

ในช่วงเริ่มต้นที่ฉันเรียนรู้ Haskell ฉันมักจะสงสัยในฟังก์ชั่นด้านล่าง:

foo 5 = 6

foo True = False

พวกเขาทั้งสองตอบสนองลายเซ็นประเภทด้านบนแล้วทำไมคน Haskell อ้างว่าเป็นidคนเดียวที่ตอบสนองลายเซ็นประเภท?

นั่นเป็นเพราะมีการforallซ่อนอยู่โดยนัยในลายเซ็นประเภท ประเภทที่แท้จริงคือ:

id :: forall a. a -> a

ดังนั้นตอนนี้ให้เรากลับมาที่แถลง: ข้อ จำกัด ปลดปล่อยเสรีภาพ จำกัด

แปลว่าระบบประเภทคำสั่งนี้จะกลายเป็น:

ข้อ จำกัด ในระดับประเภทกลายเป็นเสรีภาพในระดับคำ

และ

เสรีภาพในระดับประเภทกลายเป็นข้อ จำกัด ในระดับคำ


ให้เราลองพิสูจน์ข้อความแรก:

ข้อ จำกัด ที่ระดับประเภท ..

ดังนั้นการวางข้อ จำกัด ลงบนลายเซ็นประเภทของเรา

foo :: (Num a) => a -> a

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

foo 5 = 6
foo 4 = 2
foo 7 = 9
...

เดียวกันสามารถสังเกตได้โดยการบังคับ aกับประเภทอื่น ๆ ฯลฯ

ดังนั้นตอนนี้ลายเซ็นประเภทนี้foo :: (Num a) => a -> aแปลว่า:

a , st a -> a, a  Num

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

ดังนั้นเราสามารถเห็นการเพิ่มข้อ จำกัด (ซึ่งaควรเป็นของชุดตัวเลข) ปลดปล่อยระดับคำให้มีการใช้งานที่เป็นไปได้หลายอย่าง


ตอนนี้มาถึงประโยคที่สองและอันที่จริงแล้วมีคำอธิบายforall:

เสรีภาพในระดับประเภทกลายเป็นข้อ จำกัด ในระดับคำ

ตอนนี้ให้เราปลดปล่อยฟังก์ชั่นที่ระดับประเภท:

foo :: forall a. a -> a

ตอนนี้แปลเป็น:

a , a -> a

ซึ่งหมายความว่าการใช้งานของลายเซ็นประเภทนี้ควรเป็นเช่นนั้นa -> aสำหรับทุกสถานการณ์

ดังนั้นตอนนี้สิ่งนี้เริ่ม จำกัด เราในระดับคำ เราไม่สามารถเขียนได้อีกต่อไป

foo 5 = 7

เพราะการดำเนินการนี้จะไม่ตอบสนองถ้าเราใส่เป็นa อาจจะเป็นหรือหรือประเภทข้อมูลที่กำหนดเอง ในทุกกรณีควรส่งคืนสิ่งที่มีลักษณะคล้ายกัน เสรีภาพในระดับประเภทนี้คือสิ่งที่เรียกว่า Universal Quantification และฟังก์ชันเดียวที่สามารถสนองความต้องการนี้ได้BoolaChar[Char]

foo a = a

ซึ่งเป็นที่รู้จักกันทั่วไปว่าเป็นidentityฟังก์ชั่น


ดังนั้นforallเป็นlibertyระดับประเภทซึ่งมีวัตถุประสงค์ที่แท้จริงคือconstrainระดับคำเพื่อการใช้งานเฉพาะ


9

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

อาจเป็นการดีที่สุดที่จะอ่านและทำความเข้าใจสิ่งทั้งสองแยกจากกันแทนที่จะพยายามหาคำอธิบายว่าทำไม 'forall' จึงเป็นไวยากรณ์ที่เหมาะสมทั้งในเวลาเดียวกัน


3

อัตถิภาวนิยมเป็นอย่างไร

ด้วยอัตถิภาวนิยม-ปริมาณ, forallในdataคำจำกัดความหมายความว่าค่าที่มีอยู่สามารถเป็นที่ใด ๆประเภทที่เหมาะสมไม่ว่ามันจะต้องเป็นของทุกประเภทที่เหมาะสม - คำตอบของ yachiru

คำอธิบายว่าทำไมforallในdataความหมายที่จะ isomorphic (exists a. a)(หลอก Haskell) สามารถพบได้ในตำราของ "Haskell / ประเภทวัด existentially"

ต่อไปนี้เป็นข้อมูลสรุปคำต่อคำแบบย่อ:

data T = forall a. MkT a -- an existential datatype
MkT :: forall a. a -> T -- the type of the existential constructor

เมื่อการจับคู่รูปแบบ / การแยกโครงสร้างMkT xชนิดของxคืออะไร

foo (MkT x) = ... -- -- what is the type of x?

xสามารถเป็นประเภทใดก็ได้ (ตามที่ระบุไว้ในforall) และดังนั้นจึงเป็นประเภท:

x :: exists a. a -- (pseudo-Haskell)

ดังนั้นต่อไปนี้คือ isomorphic:

data T = forall a. MkT a -- an existential datatype
data T = MkT (exists a. a) -- (pseudo-Haskell)

forall หมายถึง forall

การตีความที่เรียบง่ายของฉันเกี่ยวกับสิ่งนี้คือ " forallจริงๆหมายถึง 'สำหรับทุกคน'" ความแตกต่างที่สำคัญที่จะทำให้เป็นผลกระทบของforallในความหมายเมื่อเทียบกับฟังก์ชั่นการประยุกต์ใช้

forallหมายถึงความหมายของค่าหรือฟังก์ชั่นจะต้อง polymorphic

หากสิ่งที่ถูกกำหนดเป็นค่า polymorphic ก็หมายความว่าค่านั้นจะต้องถูกต้องสำหรับทุกสิ่งที่เหมาะสมaซึ่งค่อนข้าง จำกัด

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

ถ้า a forallอยู่ในประเภทของฟังก์ชั่นพารามิเตอร์ (เช่น a Rank2Type) ก็หมายความว่าพารามิเตอร์ที่ใช้จะต้องเป็นpolymorphic อย่างแท้จริงเพื่อให้สอดคล้องกับแนวคิดของการforallหมายถึงคำนิยามคือ polymorphic ในกรณีนี้ประเภทของพารามิเตอร์สามารถเลือกได้มากกว่าหนึ่งครั้งในการกำหนดฟังก์ชั่น ( "และถูกเลือกโดยการใช้งานฟังก์ชั่น" ตามที่นอร์แมนชี้ )

ดังนั้นเหตุผลที่ว่าทำไมอัตถิภาวนิยมdataคำจำกัดความช่วยใด ๆ aเป็นเพราะตัวสร้างข้อมูลที่เป็น polymorphic ฟังก์ชั่น :

MkT :: forall a. a -> T

ชนิดของ MkT :: a -> *

ซึ่งหมายความว่าaอาจนำไปใช้กับฟังก์ชั่นใด ๆ เมื่อเทียบกับพูดเป็นค่า polymorphic :

valueT :: forall a. [a]

ชนิดของค่า T :: a

ซึ่งหมายความว่านิยามของ valueT ต้องเป็น polymorphic ในกรณีนี้valueTสามารถกำหนดเป็นรายการว่าง[]ทุกประเภท

[] :: [t]

ความแตกต่าง

แม้ว่าความหมายสำหรับความforallสอดคล้องExistentialQuantificationและRankNTypeมีอยู่มีความแตกต่างตั้งแต่ตัวdataสร้างสามารถใช้ในการจับคู่รูปแบบ ดังที่บันทึกไว้ในคู่มือผู้ใช้ ghc :

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

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