มีเหตุผลที่จะมีประเภทด้านล่างในภาษาการเขียนโปรแกรมหรือไม่?


49

ประเภทด้านล่างเป็นโครงสร้างที่ปรากฏในทฤษฎีประเภทคณิตศาสตร์เป็นหลัก มันจะเรียกว่าประเภทที่ว่างเปล่า มันเป็นประเภทที่ไม่มีค่า แต่เป็นประเภทย่อยของทุกประเภท

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

อะไรคือสิ่งที่มีประเภทแปลก ๆ นี้ในภาษาการเขียนโปรแกรม? มันไม่ใช่เรื่องธรรมดา แต่มีอยู่ในบางอย่างเช่น Scala และ Lisp


2
@SargeBorsch: คุณแน่ใจหรือไม่ อย่างชัดเจนแน่นอนหนึ่งไม่สามารถใน C กำหนดvoidข้อมูล ...
Basile Starynkevitch

3
@BasileStarynkevitch ไม่มีค่าประเภทvoidและประเภทหน่วยจะต้องมีหนึ่งค่า นอกจากนี้ในขณะที่คุณชี้ให้เห็นคุณไม่สามารถแม้แต่จะประกาศค่าประเภทvoidซึ่งหมายความว่ามันไม่ได้เป็นประเภทเพียงกรณีมุมพิเศษในภาษา
Sarge Borsch

2
ใช่ C เป็นสิ่งที่แปลกประหลาดในเรื่องนี้โดยเฉพาะอย่างยิ่งในการเขียนตัวชี้และประเภทตัวชี้ฟังก์ชั่น แต่voidใน Java เกือบจะเหมือนกัน: ไม่ใช่ประเภท & ไม่มีค่า
Sarge Borsch

3
ในความหมายของภาษาที่มีประเภทด้านล่างประเภทด้านล่างไม่ได้รับการพิจารณาว่าไม่มีค่า แต่จะมีค่าเดียวคือค่าที่ต่ำที่สุดซึ่งแสดงถึงการคำนวณที่ไม่เคยเสร็จสมบูรณ์ (โดยปกติ) เนื่องจากค่าด้านล่างเป็นค่าของทุกประเภทประเภทด้านล่างจึงอาจเป็นประเภทย่อยของทุกประเภท
Theodore Norvell

4
@BasileStarynkevitch Common Lisp มีประเภทศูนย์ซึ่งไม่มีค่าใด ๆ นอกจากนี้ยังมีประเภท nullซึ่งมีเพียงหนึ่งค่าสัญลักษณ์nil(aka, ()) ซึ่งเป็นหน่วยประเภท
Joshua Taylor

คำตอบ:


33

ฉันจะยกตัวอย่างง่ายๆ: C ++ vs Rust

นี่คือฟังก์ชั่นที่ใช้ในการโยนข้อยกเว้นใน C ++ 11:

[[noreturn]] void ThrowException(char const* message,
                                 char const* file,
                                 int line,
                                 char const* function);

และนี่คือสิ่งเดียวกันใน Rust:

fn formatted_panic(message: &str, file: &str, line: isize, function: &str) -> !;

ในเรื่องวากยสัมพันธ์แท้ ๆ โครงสร้างของ Rust นั้นมีเหตุผลมากกว่า โปรดทราบว่าการสร้าง C ++ ระบุประเภทผลตอบแทนแม้ว่ามันจะระบุว่ามันจะไม่กลับมา มันแปลกไปหน่อย

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


ตอนนี้เพื่อประโยชน์หรือไม่

ความจริงที่ว่าฟังก์ชั่นไม่ส่งคืนอาจมีประโยชน์สำหรับ:

  • การปรับให้เหมาะสม: เราสามารถตัดรหัสใด ๆ หลังจากมัน (มันจะไม่ส่งคืน), ไม่จำเป็นต้องบันทึกการลงทะเบียน (เนื่องจากไม่จำเป็นต้องกู้คืนรหัส), ...
  • การวิเคราะห์แบบสแตติก: กำจัดจำนวนเส้นทางการประมวลผล
  • การบำรุงรักษา: (ดูการวิเคราะห์แบบคงที่ แต่โดยมนุษย์)

6
voidในตัวอย่าง C ++ ของคุณจะกำหนดประเภทของฟังก์ชั่น - ไม่ใช่ประเภทที่ส่งคืน มันจะ จำกัด ค่าฟังก์ชั่นที่ได้รับอนุญาตreturn; สิ่งใดก็ตามที่สามารถแปลงเป็นโมฆะ (ซึ่งก็คืออะไร) หากฟังก์ชั่นreturnนั้นจะต้องไม่ถูกตามด้วยค่า void () (char const*, char const*, int, char const *)ชนิดเต็มรูปแบบของฟังก์ชั่น + 1 สำหรับใช้char constแทนconst char:-)
ชัดเจนกว่า

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

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

เป็น[[noreturn]] ไวยากรณ์ของหรือเพิ่มการทำงาน?
Zaibis

1
[ต่อ] โดยรวมแล้วฉันแค่บอกว่าการอภิปรายเกี่ยวกับข้อดีของ⊥ต้องกำหนดสิ่งที่มีคุณสมบัติเป็นการใช้งานของ⊥; และฉันไม่คิดว่าระบบประเภทที่ไม่มี ( a →⊥) ≤ ( ab ) เป็นการใช้งานที่มีประโยชน์ของ⊥ ดังนั้นในแง่นี้ SysV x86-64 C ABI (ในกลุ่มอื่น ๆ ) ก็ไม่อนุญาตให้ใช้งาน⊥
Alex Shpilkin

26

คำตอบของ Karl นั้นดี นี่คือการใช้งานเพิ่มเติมที่ฉันไม่คิดว่ามีใครพูดถึง ประเภทของ

if E then A else B

ควรเป็นชนิดที่มีค่าทั้งหมดที่อยู่ในประเภทของที่และค่าทั้งหมดที่อยู่ในประเภทของA Bถ้าชนิดของBเป็นNothingแล้วประเภทของการแสดงออกสามารถเป็นประเภทของif Aฉันมักจะประกาศกิจวัตรประจำวัน

def unreachable( s:String ) : Nothing = throw new AssertionError("Unreachable "+s) 

เพื่อบอกว่าไม่สามารถใช้รหัสได้ ตั้งแต่ชนิดของมันคือNothing, unreachable(s)ขณะนี้คุณสามารถใช้ในการใด ๆifหรือ (มักจะมากกว่า) switchโดยไม่มีผลต่อประเภทของผล ตัวอย่างเช่น

 val colour : Colour := switch state of
         BLACK_TO_MOVE: BLACK
         WHITE_TO_MOVE: WHITE
         default: unreachable("Bad state")

สกาล่ามีประเภทไม่มีอะไร

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

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

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

Haskell ทำสิ่งต่าง ๆ เล็กน้อย ใน Haskell, forall a.aการแสดงออกที่ไม่ก่อให้เกิดความคุ้มค่าสามารถมีโครงการประเภท ตัวอย่างของรูปแบบประเภทนี้จะรวมกับประเภทอื่น ๆ ดังนั้นมันจึงทำหน้าที่เป็นประเภทล่างได้อย่างมีประสิทธิภาพแม้ว่า (มาตรฐาน) Haskell ไม่มีความคิดในการพิมพ์ย่อย ยกตัวอย่างเช่นฟังก์ชั่นจากโหมโรงมาตรฐานมีประเภทโครงการerror forall a. [Char] -> aดังนั้นคุณสามารถเขียน

if E then A else error ""

และประเภทของการแสดงออกจะเป็นเช่นเดียวกับประเภทของสำหรับการแสดงออกใดAA

รายการว่างเปล่าใน Haskell forall a. [a]มีโครงการประเภท ถ้าAเป็นนิพจน์ที่มีประเภทเป็นชนิดรายการ

if E then A else []

Aคือการแสดงออกที่มีประเภทเดียวกับ


ความแตกต่างระหว่างประเภทforall a . [a]และประเภท[a]ใน Haskell คืออะไร? ไม่ได้พิมพ์ตัวแปรเชิงปริมาณในระดับสากลในการแสดงออกประเภท Haskell?
Giorgio

@Giorgio In Haskell การนับปริมาณสากลมีความหมายว่าชัดเจนว่าคุณกำลังดูรูปแบบของประเภท คุณไม่สามารถแม้แต่จะเขียนforallในมาตรฐาน Haskell 2010 ฉันเขียนปริมาณอย่างชัดเจนเพราะนี่ไม่ใช่ฟอรัม Haskell และบางคนอาจไม่คุ้นเคยกับอนุสัญญา Haskell ดังนั้นจึงไม่มีความแตกต่างยกเว้นที่forall a . [a]ไม่ได้มาตรฐานในขณะที่[a]เป็น
Theodore Norvell

19

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

  • ประเภทด้านล่าง (ฉันจะเรียกมันว่าVacuous) มีค่าเป็นศูนย์†
  • ประเภทหน่วยมีค่าเดียว ()ฉันจะเรียกทั้งสองชนิดและความคุ้มค่าเดียว
  • องค์ประกอบ (ซึ่งภาษาการเขียนโปรแกรมส่วนใหญ่สนับสนุนโดยตรงค่อนข้างผ่านบันทึก / structs / คลาสที่มีเขตข้อมูลสาธารณะ) เป็นการดำเนินงานผลิตภัณฑ์ ยกตัวอย่างเช่น(Bool, Bool)มีค่าที่เป็นไปสี่คือ(False,False), (False,True), และ(True,False) ประเภทหน่วยเป็นองค์ประกอบตัวตนของการดำเนินการจัดองค์ประกอบ เช่นและเป็นค่าของประเภทเท่านั้นดังนั้นประเภทนี้ isomorphic กับตัวเอง(True,True)
    ((), False)((), True)((), Bool)Bool
  • ประเภททางเลือกนั้นค่อนข้างจะถูกมองข้ามในภาษาส่วนใหญ่ (ภาษา OO ที่สนับสนุนพวกเขาด้วยการสืบทอด) แต่ก็ไม่มีประโยชน์อะไรเลย ทางเลือกระหว่างสองประเภทAและBโดยทั่วไปมีค่าทั้งหมดของAบวกค่าทั้งหมดของBจึงประเภทผลรวม ยกตัวอย่างเช่นEither () Boolมีสามค่าฉันจะเรียกพวกเขาLeft (), และRight False ประเภทด้านล่างเป็นองค์ประกอบตัวตนของผลรวม: มีเพียงค่าของแบบฟอร์มเพราะไม่สมเหตุสมผล ( ไม่มีค่า)Right True
    Either Vacuous ARight aLeft ...Vacuous

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

ตอนนี้คุณสามารถไปได้ไกลด้วยความกังวลเพียงด้านเดียวของปัญหา (monoid องค์ประกอบ) จากนั้นคุณไม่จำเป็นต้องใช้ประเภทล่างอย่างชัดเจน ตัวอย่างเช่นแม้แต่ Haskell ก็ทำมานานแล้วไม่มีประเภทก้นมาตรฐาน Voidตอนนี้มันได้ก็เรียกว่า

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

แน่นอนคุณอาจจะบอกว่านี่เป็นสิ่ง theoretists' ไร้สาระทั่วไป คุณไม่จำเป็นต้องรู้เกี่ยวกับทฤษฎีหมวดหมู่เลยเพื่อที่จะเป็นโปรแกรมเมอร์ที่ดี แต่เมื่อคุณทำมันจะให้วิธีที่ทรงพลังและไร้สาระทั่วไปแก่คุณในการให้เหตุผลเกี่ยวกับโค้ดและพัฒนาค่าคงที่


mb21 ทำให้ผมนึกถึงที่จะทราบว่านี้ไม่ควรจะสับสนกับค่าด้านล่าง ในภาษาขี้เกียจเหมือน Haskell, ทุกชนิดมีด้านล่าง“ค่า” ชี้แนะ นี่ไม่ใช่สิ่งที่เป็นรูปธรรมที่คุณสามารถผ่านไปได้อย่างชัดเจน แต่มันคือสิ่งที่“ คืน” ตัวอย่างเช่นเมื่อฟังก์ชั่นวนซ้ำตลอดไป แม้แต่Voidประเภทของ Haskell “ มี” ค่าล่างจึงเป็นชื่อ ในแง่มุมนั้นประเภทล่างของ Haskell มีค่าหนึ่งค่าและประเภทของหน่วยมีสองค่า แต่ในการอภิปรายตามหมวดหมู่ทฤษฎีจะไม่สนใจค่านี้


"ประเภทด้านล่าง (ฉันจะเรียกมันว่าVoid)" ซึ่งเป็นเพื่อไม่ให้สับสนกับค่า bottomซึ่งเป็นสมาชิกของชนิดใด ๆใน Haskell
mb21

18

บางทีมันอาจจะวนซ้ำไปตลอดกาลหรืออาจจะเป็นข้อยกเว้น

ฟังดูเหมือนเป็นประเภทที่มีประโยชน์ในสถานการณ์เหล่านั้น

นอกจากนี้แม้ว่าNothing(ชื่อของ Scala สำหรับประเภทด้านล่าง) สามารถไม่มีค่าได้ แต่List[Nothing]ก็ไม่มีข้อ จำกัด เช่นนั้นซึ่งทำให้มีประโยชน์ในฐานะประเภทของรายการที่ว่างเปล่า ภาษาส่วนใหญ่ได้รับสิ่งนี้โดยการทำให้รายการของสตริงว่างต่างจากรายการของจำนวนเต็มที่ว่างเปล่า แต่เป็นรายการที่ว่างเปล่าที่จะเขียนซึ่งเป็นข้อเสียเปรียบครั้งใหญ่ในภาษาที่เน้นรายการ


12
“ รายการที่ว่างเปล่าของ Haskell เป็นตัวสร้างประเภท”: แน่นอนว่าสิ่งที่เกี่ยวข้องเกี่ยวกับที่นี่เป็นมากกว่านั้นคือpolymorphicหรือoverloaded - นั่นคือรายการที่ว่างจากประเภทที่แตกต่างกันเป็นค่าที่แตกต่างกัน แต่[]แสดงถึงพวกเขาทั้งหมด ประเภทเฉพาะตามความจำเป็น
Peter LeFanu Lumsdaine

ที่น่าสนใจ: ถ้าคุณพยายามที่จะสร้างอาร์เรย์ที่ว่างเปล่าในล่าม Haskell [a]คุณจะได้รับความคุ้มค่าที่ชัดเจนมากกับประเภทที่ไม่แน่นอนมาก: ในทำนองเดียวกันอัตราผลตอบแทน:t Left 1 Num a => Either a bการประเมินผลการแสดงออกจริงบังคับประเภทของaแต่ไม่ใช่b:Either Integer b
John Dvorak

5
รายการว่างเปล่าเป็นตัวสร้างค่า บิตที่สับสนผู้สร้างประเภทที่เกี่ยวข้องมีชื่อเดียวกัน แต่รายการเปล่านั้นเป็นค่าไม่ใช่ประเภท (ดีมีรายการระดับประเภทด้วย แต่นั่นเป็นหัวข้ออื่นทั้งหมด) ส่วนที่ทำให้การทำงานของรายการที่ว่างเปล่าสำหรับรายการประเภทใด ๆ เป็นนัยในรูปแบบของforall forall a. [a]มีวิธีคิดที่ดีอยู่บ้างforallแต่ใช้เวลาพอสมควรในการคิดออก
David

@PeterLeFanuLumsdaine นั่นคือสิ่งที่เป็นตัวสร้างประเภทหมายถึง *มันก็หมายความว่ามันเป็นคนประเภทที่มีชนิดที่แตกต่างจาก
GregRos

2
ใน Haskell []เป็นตัวสร้างประเภทและ[]เป็นนิพจน์ที่แสดงรายการว่าง แต่นั่นไม่ได้หมายความว่า "รายการว่างเปล่าของ Haskell เป็นตัวสร้างประเภท" บริบททำให้ชัดเจนว่า[]กำลังถูกใช้เป็นประเภทหรือเป็นนิพจน์ สมมติว่าคุณประกาศdata Foo x = Foo | Bar x (Foo x); ตอนนี้คุณสามารถใช้Fooเป็นคอนสตรัคเตอร์ประเภทหรือเป็นค่าได้ แต่มันก็เกิดขึ้นที่คุณเลือกชื่อเดียวกันสำหรับทั้งคู่
Theodore Norvell

3

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

int F(int arg) {
 if (arg != 0)
  return arg + 1; //some computation
 else
  Assert(false); //this throws but the compiler does not know that
}
void Assert(bool cond) { if (!cond) throw ...; }

คอมไพเลอร์จะบ่นว่าFจะไม่ส่งคืนสิ่งใดในรหัสเส้นทางอย่างน้อยหนึ่งรายการ ถ้าAssertจะถูกทำเครื่องหมายว่าไม่คืนคอมไพเลอร์ก็ไม่จำเป็นต้องเตือน


2

ในบางภาษาnullมีประเภทด้านล่างตั้งแต่ย่อยทุกประเภทอย่างกำหนดสิ่งที่ใช้ภาษา null สำหรับ (แม้จะมีความขัดแย้งรุนแรงของการมีnullเป็นได้ทั้งตัวเองและฟังก์ชั่นที่ส่งกลับตัวเองหลีกเลี่ยงการขัดแย้งกันเกี่ยวกับเหตุผลที่botควรจะร้าง)

นอกจากนี้ยังสามารถใช้เป็นฟังก์ชั่น catch-all in ( any -> bot) เพื่อจัดการการจัดส่งได้อย่างยอดเยี่ยม

และบางภาษาช่วยให้คุณสามารถแก้ไขbotเป็นข้อผิดพลาดได้จริงซึ่งสามารถใช้เพื่อระบุข้อผิดพลาดของคอมไพเลอร์แบบกำหนดเอง


11
ไม่ประเภทด้านล่างไม่ใช่ประเภทของหน่วย ประเภทด้านล่างไม่มีค่าเลยดังนั้นฟังก์ชันที่ส่งคืนประเภทด้านล่างไม่ควรส่งคืน (เช่นการยกเว้นหรือวนซ้ำไปเรื่อย ๆ )
Basile Starynkevitch

@BasileStarynkevitch - ฉันไม่ได้พูดถึงประเภทของหน่วย ประเภทหน่วยแมปกับvoidในภาษาทั่วไป (แม้จะมีความหมายแตกต่างกันเล็กน้อยสำหรับการใช้งานเหมือนกัน) nullไม่ได้ แม้ว่าคุณจะพูดถูกว่าภาษาส่วนใหญ่จะไม่ทำตัวเป็นโมฆะเป็นประเภทล่าง
Telastyn

3
@ TheodoreNorvell - Tangentรุ่นแรกทำอย่างนั้น - แม้ว่าฉันจะเป็นผู้แต่งก็ตามบางทีนั่นอาจเป็นการโกง ฉันไม่ได้บันทึกลิงก์ไว้สำหรับคนอื่นและมันก็ไม่นานหลังจากที่ฉันทำวิจัยนั้น
Telastyn

1
@Martijn แต่คุณสามารถใช้ได้nullเช่นคุณเปรียบเทียบตัวชี้ไปยังnullผลลัพธ์แบบบูลีน ฉันคิดว่าคำตอบนั้นแสดงว่ามีประเภทก้นที่แตกต่างกันสองแบบ (a) ภาษา (เช่น Scala) ซึ่งประเภทที่เป็นประเภทย่อยของทุกประเภทแสดงถึงการคำนวณที่ไม่ส่งผลลัพธ์ใด ๆ โดยพื้นฐานแล้วมันเป็นประเภทที่ว่างเปล่าแม้ว่าในทางเทคนิคมักจะมีประชากรที่มีค่าต่ำสุดที่ไร้ประโยชน์แสดงถึงการกำจัด (b) ภาษาเช่น Tangent ซึ่งประเภทด้านล่างเป็นชุดย่อยของประเภทอื่น ๆ ทุกประเภทเพราะมันมีค่าที่มีประโยชน์ที่พบได้ในทุกประเภทอื่น ๆ - null
Theodore Norvell

4
เป็นที่น่าสนใจว่าบางภาษามีค่าที่มีประเภทที่คุณไม่สามารถประกาศได้ (โดยทั่วไปสำหรับตัวอักษร null) และอื่น ๆ มีประเภทที่คุณสามารถประกาศได้ แต่ไม่มีค่าใด ๆ (ประเภทล่างแบบดั้งเดิม) และพวกเขาเติมบทบาทที่เปรียบเทียบกันได้ .
Martijn

1

ใช่นี่เป็นประเภทที่ค่อนข้างมีประโยชน์ ในขณะที่บทบาทของมันจะเป็นส่วนใหญ่ภายในระบบประเภทมีบางโอกาสที่ประเภทด้านล่างจะปรากฏในที่เปิดเผย

พิจารณาภาษาที่พิมพ์แบบคงที่ซึ่งเงื่อนไขเป็นนิพจน์ (ดังนั้นโครงสร้าง if-then-else จะเพิ่มเป็นสองเท่าในโอเปอร์เรเตอร์ของ C และเพื่อนและอาจมีคำสั่งกรณีหลายทางที่คล้ายกัน) ภาษาโปรแกรมเชิงฟังก์ชั่นมีสิ่งนี้ แต่มันก็เกิดขึ้นในบางภาษาที่จำเป็นเช่นกัน (ตั้งแต่ ALGOL 60) จากนั้นนิพจน์สาขาทั้งหมดจะต้องสร้างประเภทของนิพจน์เงื่อนไขทั้งหมดในท้ายที่สุด หนึ่งอาจต้องการประเภทของพวกเขาจะเท่ากัน (และฉันคิดว่านี่เป็นกรณีสำหรับผู้ประกอบการที่สามใน C) แต่นี่เป็นข้อ จำกัด มากเกินไปโดยเฉพาะเมื่อเงื่อนไขยังสามารถใช้เป็นคำสั่งตามเงื่อนไข (ไม่คืนค่าที่เป็นประโยชน์ใด ๆ ) โดยทั่วไปแล้วหนึ่งต้องการให้แต่ละสาขานิพจน์เป็นแปลง (โดยปริยาย) ประเภททั่วไปที่จะเป็นประเภทของการแสดงออกเต็มรูปแบบ (อาจมีข้อ จำกัด ที่ซับซ้อนมากขึ้นหรือน้อยลงเพื่อให้ประเภททั่วไปสามารถพบได้อย่างมีประสิทธิภาพโดย complier, cf. C ++ แต่ฉันจะไม่เข้าไปดูรายละเอียดเหล่านี้ที่นี่)

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

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


ดังนั้นความคิดที่var foo = someCondition() ? functionReturningBar() : functionThatAlwaysThrows()สามารถอนุมานประเภทของการfooเป็นBarเพราะการแสดงออกไม่สามารถให้ผลอย่างอื่น?
supercat

1
คุณเพิ่งอธิบายประเภทหน่วย - อย่างน้อยในส่วนแรกของคำตอบของคุณ ฟังก์ชั่นซึ่งผลตอบแทนประเภทหน่วยเป็นเช่นเดียวกับหนึ่งซึ่งถูกประกาศเป็นที่กลับมาvoidในซีส่วนที่สองของคำตอบของคุณที่คุณพูดคุยเกี่ยวกับประเภทสำหรับฟังก์ชั่นที่ไม่เคยส่งกลับหรือรายการที่ไม่มีองค์ประกอบที่เป็นจริง ประเภทด้านล่าง! (มันมักจะเขียนเป็น_|_มากกว่า*ไม่แน่ใจว่าทำไมอาจเป็นเพราะด้านล่าง (มนุษย์) :)
andrewf

2
สำหรับการหลีกเลี่ยงข้อสงสัย: 'ไม่ส่งคืนสิ่งที่มีประโยชน์' แตกต่างจาก 'ไม่ส่งคืน'; ครั้งแรกจะถูกแสดงด้วยประเภทหน่วย; ครั้งที่สองโดยประเภทด้านล่าง
andrewf

@ andrewf: ใช่ฉันเข้าใจความแตกต่าง คำตอบของฉันค่อนข้างยาว แต่ประเด็นที่ฉันต้องการคือหน่วยประเภทและประเภทด้านล่างมีบทบาทที่แตกต่างกัน (แต่แตกต่างกัน) เพื่อให้สามารถใช้นิพจน์บางอย่างยืดหยุ่นมากขึ้น (แต่ยังปลอดภัย)
Marc van Leeuwen

@supercat: ใช่นั่นคือความคิด ขณะนี้อยู่ใน c ++ ที่ผิดกฎหมายแม้ว่ามันจะถูกต้องถ้า functionThatAlwaysThrows()ถูกแทนที่ด้วยความชัดเจนthrowเนื่องจากภาษาเป็นพิเศษในมาตรฐาน มีประเภทที่ไม่นี้จะเป็นการปรับปรุง
Marc van Leeuwen

0

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

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

"bottom" จะเป็นประเภทที่ไม่มีค่าที่เป็นไปได้ มันไม่มีทางเป็นไปได้ หากฟังก์ชันส่งคืน "bottom" จะไม่สามารถส่งคืนได้จริงเนื่องจากไม่มีค่าประเภท "bottom" ที่สามารถส่งคืนได้

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

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