มันสมเหตุสมผลไหมที่จะมีทั้งแนวคิดของ 'null' และ 'บางที'?


11

ในขณะที่สร้างไคลเอนต์สำหรับเว็บ APIใน C # ฉันพบปัญหาเกี่ยวกับnullค่าที่มันจะแสดงถึงสองสิ่งที่แตกต่างกัน:

  • ไม่มีอะไรเช่นfooอาจหรือไม่มีbar
  • ไม่รู้จัก : โดยค่าเริ่มต้นการตอบสนองของ API จะรวมเฉพาะชุดย่อยของคุณสมบัติเท่านั้นคุณจะต้องระบุคุณสมบัติเพิ่มเติมที่คุณต้องการ ไม่ทราบดังนั้นจึงหมายความว่าไม่ได้ขอคุณสมบัติจาก API

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

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


9
มันไม่ได้ทำให้ Sanse nullที่จะมี มันเป็นความคิดที่แตกสลายอย่างทั่วถึง
Andrej Bauer

7
อย่าทำบางทีอาจจะเป็น foo ที่มีความหมายที่แตกต่างจากบางที foo อาจเป็นmonadและหนึ่งในกฎ monad ก็คือM M xและM xควรมีความหมายเดียวกัน
Eric Lippert

2
คุณอาจพิจารณาดูการออกแบบของ Visual Basic รุ่นแรกซึ่งไม่มีสิ่งใด (อ้างอิงถึงไม่มีวัตถุ), Null (ซีแมนติคอลโมฆะฐานข้อมูล), ว่างเปล่า (ตัวแปรไม่ได้เริ่มต้น) และ Missing (พารามิเตอร์ทางเลือกไม่ได้ผ่าน) การออกแบบนี้มีความซับซ้อนและในหลาย ๆ วิธีไม่สอดคล้องกัน แต่มีเหตุผลว่าทำไมแนวคิดเหล่านั้นไม่ได้พูดคุยกัน
Eric Lippert

3
ใช่ฉันจะไม่ใช้บางทีบางทีอย่างใดอย่างหนึ่ง แต่ในความเป็นจริงเช่นMaybe aเดียวกับความหมายเหมือนกับและ isomorphic ที่จะพิมพ์จาก @Andej คำตอบ คุณสามารถกำหนดอินสแตนซ์ monad ของคุณเองสำหรับประเภทนี้ด้วยและดังนั้นจึงใช้ combinators monad ที่แตกต่าง a + 1 + 1a+1Maybe Maybe aa+1+1UserInput a
Euge

12
@EricLippert: มันเป็นเท็จว่า " M (M x)และM xควรมีความหมายเดียวกัน" ใช้M = Listเช่น: รายชื่อของรายการไม่ได้ในสิ่งเดียวกับรายการ เมื่อMเป็น monad มีการเปลี่ยนแปลง (คือการคูณ monad) จากM (M x)การM xที่อธิบายความสัมพันธ์ระหว่างพวกเขา แต่พวกเขาไม่ได้มี "ความหมายเดียวกัน"
Andrej Bauer

คำตอบ:


14

nullค่าเป็นของขวัญเริ่มต้นทุกที่เป็นเพียงความคิดเสียจริงๆดังนั้นลืมเกี่ยวกับว่า

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

ผู้ที่ออกแบบไลบรารีมาตรฐานพยายามคาดเดารูปแบบการใช้งานทั่วไปและพวกเขามักจะทำได้ดี แต่ถ้ามีสิ่งที่คุณต้องการคุณควรกำหนดด้วยตัวคุณเอง ตัวอย่างเช่นใน Haskell คุณสามารถกำหนด:

data UnknownNothing a =
     Unknown
   | Nothing
   | Value a

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

data UserInput a =
     Unknown
   | NotGiven
   | Answer a

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


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

หากอุปสรรคในการสร้างประเภทดังกล่าวลดลงในหลายภาษา : /
Raphael

หากมีเพียงคนหยุดใช้ภาษาจากยุค 1960 (หรือกลับชาติมาเกิดจากปี 1980)
Andrej Bauer

@ AndrejBauer: อะไรนะ แต่นักทฤษฎีก็คงไม่มีอะไรจะบ่น!
Yttrill

เราไม่บ่นเราสูงขึ้น
Andrej Bauer

4

เท่าที่ฉันรู้ค่าnullใน C # เป็นค่าที่เป็นไปได้สำหรับตัวแปรบางอย่างขึ้นอยู่กับประเภทของมัน (ฉันใช่ไหม?) ตัวอย่างเช่นอินสแตนซ์ของบางคลาส สำหรับส่วนที่เหลือของประเภท (เช่นint, boolฯลฯ ) คุณสามารถเพิ่มค่าข้อยกเว้นนี้โดยการประกาศตัวแปรด้วยint?หรือbool?แทน (นี่คือสิ่งที่ตัวMaybeสร้างทำตามที่ฉันจะอธิบายต่อไป)

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

IMO แนวทางสุดท้ายนี้ดีกว่าสำหรับภาษาโปรแกรมใด ๆ


ใช่ความเข้าใจ C # ของคุณถูกต้อง ดังนั้นฉันสามารถอนุมานจากคำตอบของคุณที่คุณแนะนำการทำรังMaybeและทิ้งnullทั้งหมด?
Stijn

2
ความคิดเห็นของฉันเกี่ยวกับวิธีการใช้ภาษา ฉันไม่รู้วิธีปฏิบัติที่ดีที่สุดของการเขียนโปรแกรม C # ในกรณีของคุณตัวเลือกที่ดีที่สุดคือการกำหนดประเภทข้อมูลตามที่ @Andrej Bauer อธิบาย แต่ฉันคิดว่าคุณไม่สามารถทำได้ใน C #
Euge

@Euge: ประเภทพีชคณิตสามารถประมาณ (คร่าว ๆ ) กับ subtyping สไตล์ OO คลาสสิกและ subtype polymorphic การส่งข้อความ นั่นเป็นวิธีที่ทำได้ใน Scala เช่นมีการบิดเพิ่มเติมเพื่ออนุญาตสำหรับประเภทปิด ประเภทจะกลายเป็น superclass ที่เป็นนามธรรม, ตัวสร้างประเภทกลายเป็นคลาสย่อยคอนกรีต, การจับคู่รูปแบบบนตัวสร้างสามารถประมาณได้โดยการย้ายเคสไปเป็นวิธีการโอเวอร์โหลดในคลาสย่อยหรือisinstanceการทดสอบคอนกรีต สกาล่ายังมีการจับคู่รูปแบบ "เหมาะสม" ด้วยเช่นกันและจริง ๆ แล้วC♯เพิ่งได้รับรูปแบบที่เรียบง่ายเช่นกัน
Jörg W Mittag

"การบิดเพิ่มเติม" ที่ฉันพูดถึงสำหรับ Scala คือนอกเหนือจากตัวดัดแปลง "มาตรฐาน" "เสมือน" (สำหรับคลาสที่สามารถสืบทอดจาก) และfinal(สำหรับคลาสที่ไม่สามารถสืบทอดได้) ยังมีsealedสำหรับ คลาสที่สามารถขยายได้ภายในหน่วยการคอมไพล์เดียวกันเท่านั้น ซึ่งหมายความว่าคอมไพเลอร์สามารถทราบคลาสย่อยที่เป็นไปได้ทั้งหมด (หากมีทั้งหมดนั้นเป็นของตัวเองsealedหรือfinal) และทำการตรวจสอบความครบถ้วนสมบูรณ์สำหรับรูปแบบการจับคู่ซึ่งเป็นไปไม่ได้สำหรับภาษาที่โหลดโค้ดรันไทม์
Jörg W Mittag

แน่นอนคุณสามารถออกแบบประเภทที่มีความหมายใน C # โปรดทราบว่า C # ไม่อนุญาตให้ใช้ประเภทในตัว (เรียกว่า Nullable ใน C #) เพื่อซ้อนกัน int??ผิดกฎหมายใน C #
Eric Lippert

3

หากคุณสามารถกำหนดสหภาพแรงงานประเภทเช่นX or Yและถ้าคุณมีประเภทที่มีชื่อNullซึ่งหมายถึงเฉพาะnullค่า (และไม่มีอะไรอื่น) แล้วสำหรับประเภทใดT, เป็นจริงไม่แตกต่างจากT or Null Maybe Tปัญหาเดียวที่มีnullคือเมื่อภาษาที่ถือว่าเป็นชนิดที่Tเป็นT or Nullโดยปริยายทำให้nullค่าที่ถูกต้องสำหรับทุกประเภท (ยกเว้นชนิดดั้งเดิมในภาษาที่มีพวกเขา) นี่คือสิ่งที่เกิดขึ้นเช่นใน Java ที่ฟังก์ชั่นซึ่งจะเป็นยังยอมรับStringnull

หากคุณปฏิบัติต่อประเภทเป็นชุดของค่านิยมและorเป็นสหภาพของชุดนั้น(T or Null) or Nullหมายถึงชุดของค่าซึ่งเป็นเช่นเดียวกับT ∪ {null} ∪ {null} T or Nullมีคอมไพเลอร์ซึ่งทำการวิเคราะห์ประเภทนี้ (SBCL) ดังที่ได้กล่าวไว้ในความคิดเห็นคุณไม่สามารถแยกแยะความแตกต่างระหว่างMaybe TและMaybe Maybe Tเมื่อคุณดูประเภทเป็นโดเมนได้ (ในทางกลับกันมุมมองนั้นมีประโยชน์มากสำหรับโปรแกรมที่พิมพ์แบบไดนามิก) แต่คุณสามารถรักษาชนิดไว้เป็นนิพจน์เกี่ยวกับพีชคณิตซึ่ง(T or Null) or Nullแสดงถึง Maybes หลายระดับ


2
ที่จริงมันเป็นสิ่งสำคัญที่ไม่ได้เป็นสิ่งเดียวกับMaybe (Maybe X) ไม่ได้เป็นสิ่งเดียวกับ การทำให้สมกับเป็นไปไม่ได้ที่จะปฏิบัติต่อความหลากหลาย Maybe XNothingJust NothingMaybe (Maybe X)Maybe XMaybe _
Gilles 'หยุดชั่วร้าย'

1

คุณต้องค้นหาสิ่งที่คุณต้องการแสดงแล้วหาวิธีแสดงมัน

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

หากคุณต้องการที่จะเป็นตัวแทนของค่าทั้งหมดรวมทั้งค่าเพิ่มเติมทั้งสามนี้คุณจะต้องสร้าง enum ที่มีกรณี "ไม่มีอะไร", "ไม่รู้จัก", "อะไร" และ "ค่า" และไปจากที่นั่น

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

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

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