ฉันสงสัยว่าอะไรคือข้อดีของMaybe
monad ที่มีข้อยกเว้น? ดูเหมือนว่าMaybe
เป็นวิธีการทางtry..catch
ไวยากรณ์ที่ชัดเจน (และค่อนข้างใช้พื้นที่มาก)
อัปเดตโปรดทราบว่าฉันตั้งใจจะไม่พูดถึง Haskell
ฉันสงสัยว่าอะไรคือข้อดีของMaybe
monad ที่มีข้อยกเว้น? ดูเหมือนว่าMaybe
เป็นวิธีการทางtry..catch
ไวยากรณ์ที่ชัดเจน (และค่อนข้างใช้พื้นที่มาก)
อัปเดตโปรดทราบว่าฉันตั้งใจจะไม่พูดถึง Haskell
คำตอบ:
การใช้Maybe
(หรือลูกพี่ลูกน้องของมันEither
ซึ่งใช้งานได้ในลักษณะเดียวกัน แต่ให้คุณคืนค่าโดยพลการแทนNothing
) ทำหน้าที่แตกต่างกันเล็กน้อยกว่าข้อยกเว้น ในแง่ของจาวาก็เหมือนกับมีข้อยกเว้นตรวจสอบมากกว่าข้อยกเว้นรันไทม์ มันแสดงถึงสิ่งที่คาดหวังซึ่งคุณต้องจัดการแทนที่จะเป็นข้อผิดพลาดที่คุณไม่คาดคิด
ฟังก์ชันที่ต้องการindexOf
คืนMaybe
ค่าเพราะคุณคาดหวังความเป็นไปได้ที่ไอเท็มนั้นจะไม่อยู่ในรายการ มันเหมือนกับกลับมาnull
จากฟังก์ชั่นยกเว้นในวิธีที่ปลอดภัยประเภทที่บังคับให้คุณจัดการกับnull
กรณีและปัญหา ทำงานในลักษณะเดียวกันยกเว้นว่าคุณสามารถส่งกลับข้อมูลที่เกี่ยวข้องกับกรณีข้อผิดพลาดดังนั้นจึงเป็นเรื่องจริงมากขึ้นคล้ายกับข้อยกเว้นกว่าEither
Maybe
ดังนั้นข้อดีของMaybe
/ Either
แนวทางคืออะไร? สำหรับหนึ่งคนมันเป็นพลเมืองชั้นหนึ่งของภาษา ลองเปรียบเทียบฟังก์ชั่นที่ใช้Either
กับการโยนข้อยกเว้น สำหรับกรณียกเว้นการขอความช่วยเหลือที่แท้จริงเพียงอย่างเดียวของคุณคือtry...catch
คำสั่ง สำหรับEither
ฟังก์ชั่นนี้คุณสามารถใช้ combinators ที่มีอยู่เพื่อทำให้การควบคุมการไหลชัดเจนขึ้น นี่คือตัวอย่างบางส่วน:
ก่อนอื่นสมมติว่าคุณต้องการลองใช้ฟังก์ชั่นหลายอย่างที่อาจผิดพลาดในแถวจนกว่าคุณจะได้ฟังก์ชันที่ไม่เป็นเช่นนั้น หากคุณไม่ได้รับสิ่งใดโดยไม่มีข้อผิดพลาดคุณต้องการส่งคืนข้อความแสดงข้อผิดพลาดพิเศษ นี้เป็นจริงรูปแบบที่มีประโยชน์มาก try...catch
แต่จะเป็นความเจ็บปวดที่น่ากลัวโดยใช้ อย่างมีความสุขเนื่องจากEither
เป็นเพียงค่าปกติคุณสามารถใช้ฟังก์ชั่นที่มีอยู่เพื่อทำให้โค้ดมีความชัดเจนมากขึ้น:
firstThing <|> secondThing <|> throwError (SomeError "error message")
อีกตัวอย่างหนึ่งคือการมีฟังก์ชั่นเสริม สมมติว่าคุณมีหลายฟังก์ชันที่จะเรียกใช้รวมถึงฟังก์ชันที่พยายามปรับการสืบค้นให้เหมาะสม หากสิ่งนี้ล้มเหลวคุณต้องการให้ทุกอย่างทำงานอย่างอื่น คุณสามารถเขียนโค้ดบางอย่างเช่น:
do a <- getA
b <- getB
optional (optimize query)
execute query a b
ทั้งสองกรณีนี้มีความชัดเจนและสั้นกว่าการใช้try..catch
และที่สำคัญกว่านั้นคือความหมายมากขึ้น การใช้ฟังก์ชั่นเหมือน<|>
หรือoptional
ทำให้ความตั้งใจของคุณมากชัดเจนกว่าการใช้try...catch
ที่มักจะจัดการกับข้อยกเว้น
โปรดทราบว่าคุณไม่จำเป็นต้องทิ้งรหัสของคุณด้วยบรรทัดเช่นif a == Nothing then Nothing else ...
! จุดรวมของการรักษาMaybe
และEither
ในฐานะ Monad คือการหลีกเลี่ยงสิ่งนี้ คุณสามารถเข้ารหัสซีแมนทิกส์การแพร่กระจายในฟังก์ชันผูกเพื่อให้คุณได้รับการตรวจสอบโมฆะ / ข้อผิดพลาดฟรี ครั้งเดียวที่คุณต้องตรวจสอบอย่างชัดเจนคือถ้าคุณต้องการส่งคืนสิ่งอื่นนอกเหนือจากNothing
a Nothing
และแม้จะเป็นเรื่องง่าย: มีฟังก์ชั่นไลบรารีมาตรฐานมากมายที่จะทำให้โค้ดนั้นดีกว่า
ในที่สุดข้อดีอีกอย่างหนึ่งก็คือMaybe
/ Either
พิมพ์ง่ายขึ้น ไม่จำเป็นต้องขยายภาษาด้วยคำหลักเพิ่มเติมหรือโครงสร้างการควบคุมทุกอย่างเป็นเพียงห้องสมุด เนื่องจากพวกเขากำลังเพียงค่าปกติก็จะทำให้ระบบการพิมพ์ที่ง่าย - ใน Java คุณต้องแยกความแตกต่างระหว่างประเภท (เช่นพิมพ์กลับ) และผลกระทบ (เช่นthrows
งบ) Maybe
ซึ่งคุณจะไม่ได้ใช้ พวกเขายังทำงานเหมือนคนอื่น ๆ ประเภทที่ผู้ใช้กำหนด - ไม่จำเป็นต้องมีรหัสการจัดการข้อผิดพลาดพิเศษอบเป็นภาษา
การชนะอีกครั้งคือMaybe
/ Either
เป็น functors และ monads ซึ่งหมายความว่าพวกเขาสามารถใช้ประโยชน์จากฟังก์ชั่นการควบคุม monad ที่มีอยู่ (ซึ่งมีจำนวนที่พอใช้) และโดยทั่วไปแล้วก็เล่นกับ monads อื่น ๆ
ที่กล่าวว่ามีบางประการ สำหรับหนึ่งค่าMaybe
มิได้Either
แทนที่ไม่ จำกัดข้อยกเว้น คุณจะต้องการวิธีอื่นในการจัดการสิ่งต่าง ๆ เช่นการหารด้วย 0 เพียงเพราะมันเป็นความเจ็บปวดที่ทุกฝ่ายจะคืนMaybe
ค่า
ปัญหาอื่นคือการส่งคืนข้อผิดพลาดหลายประเภท (ใช้กับEither
) ด้วยข้อยกเว้นคุณสามารถโยนข้อยกเว้นประเภทต่าง ๆ ในฟังก์ชั่นเดียวกันได้ ด้วยEither
คุณจะได้รับเพียงหนึ่งประเภท สิ่งนี้สามารถเอาชนะได้ด้วยการพิมพ์ย่อยหรือ ADT ที่มีข้อผิดพลาดประเภทต่าง ๆ ทั้งหมดเป็นตัวสร้าง (วิธีที่สองนี้เป็นวิธีที่มักใช้ใน Haskell)
ถึงกระนั้นฉันก็ชอบวิธีMaybe
/ Either
เพราะฉันคิดว่ามันง่ายและยืดหยุ่นกว่า
OpenFile()
สามารถขว้างFileNotFound
หรือNoPermission
หรือTooManyDescriptors
อื่น ๆ ได้ A ไม่มีใครไม่พกข้อมูลนี้if None return None
คำสั่งสไตล์จำนวนมากสิ่งสำคัญที่สุดคือทั้งหมดข้อยกเว้นและอาจจะมีจุดประสงค์ที่แตกต่างกัน - monad มีการใช้เพื่อระบุถึงปัญหาในขณะที่อาจจะไม่ใช่
“ พยาบาลถ้ามีผู้ป่วยในห้องที่ 5 คุณช่วยให้เขารอได้ไหม?”
(แจ้งให้ทราบว่า "ถ้า" - นี่หมายถึงแพทย์คาดหวังว่าอาจจะ monad)
None
ค่าสามารถแพร่กระจายได้) จุดที่ 5 ของคุณเป็นสิ่งที่ถูกต้องเท่านั้น…คำถามคือ: สถานการณ์ใดที่มีความพิเศษอย่างไม่น่าสงสัย? มันจะเปิดออก ... ไม่มาก
bind
ในลักษณะที่การทดสอบNone
ไม่ได้เกิดขึ้นกับค่าใช้จ่ายเกี่ยวกับวากยสัมพันธ์ ตัวอย่างที่ง่ายมาก C # เพิ่งโอเวอร์โหลดตัวNullable
ดำเนินการอย่างเหมาะสม ไม่มีการตรวจสอบที่None
จำเป็นแม้เมื่อใช้งานชนิด แน่นอนว่าการตรวจสอบยังคงดำเนินการอยู่ (มันปลอดภัยสำหรับพิมพ์) แต่อยู่เบื้องหลังและไม่เกะกะรหัสของคุณ เช่นเดียวกับในบางแง่มุมของการคัดค้านของคุณต่อการคัดค้านของฉันถึง (5) แต่ฉันยอมรับว่ามันอาจไม่ได้นำไปใช้เสมอ
Maybe
เป็น monad คือการทำให้การถ่ายทอดNone
โดยปริยาย ซึ่งหมายความว่าหากคุณต้องการNone
รับคืนNone
คุณไม่ต้องเขียนรหัสพิเศษใด ๆ เลย None
ครั้งเดียวที่คุณต้องแข่งขันคือถ้าคุณต้องการที่จะทำบางสิ่งบางอย่างเป็นพิเศษเกี่ยวกับ คุณไม่จำเป็นต้องif None then None
เรียงลำดับของข้อความ
null
การตรวจสอบว่าเป็นเช่นนั้น (เช่นif Nothing then Nothing
) ให้ฟรีเพราะMaybe
เป็น monad มันเข้ารหัสในความหมายของการผูก ( >>=
) Maybe
สำหรับ
Either
) Maybe
ที่พฤติกรรมเช่นเดียวกับ สลับไปมาระหว่างทั้งสองเป็นจริงค่อนข้างง่ายเพราะเป็นจริงเพียงกรณีพิเศษของMaybe
Either
(ใน Haskell คุณอาจคิดว่าMaybe
เป็นEither ()
.)
"อาจจะ" ไม่ใช่การทดแทนสำหรับข้อยกเว้น ข้อยกเว้นมีไว้เพื่อใช้ในกรณีพิเศษ (ตัวอย่างเช่น: การเปิดการเชื่อมต่อ db และเซิร์ฟเวอร์ db ไม่อยู่ที่นั่นแม้ว่ามันควรจะเป็น) "อาจจะ" สำหรับการสร้างแบบจำลองสถานการณ์เมื่อคุณอาจหรืออาจมีค่าที่ถูกต้อง; สมมติว่าคุณได้รับค่าจากพจนานุกรมสำหรับคีย์: อาจมีหรือไม่มี - ไม่มีอะไร "พิเศษ" เกี่ยวกับผลลัพธ์เหล่านี้
ฉันคำตอบที่สองของ Tikhon แต่ฉันคิดว่ามันมีจุดปฏิบัติที่สำคัญมากที่ทุกคนขาดหายไป:
Either
กลไกไม่ได้คู่กับหัวข้อที่ทุกคนดังนั้นสิ่งที่เราเห็นทุกวันนี้ในชีวิตจริงคือโซลูชั่นการเขียนโปรแกรมแบบอะซิงโครนัสจำนวนมากกำลังนำเอารูปEither
แบบของการจัดการข้อผิดพลาดมาใช้ พิจารณาสัญญา Javascript ตามรายละเอียดในลิงก์ใด ๆ เหล่านี้:
แนวคิดของสัญญาอนุญาตให้คุณเขียนโค้ดแบบอะซิงโครนัสเช่นนี้ (นำมาจากลิงก์สุดท้าย):
var greetingPromise = sayHello();
greetingPromise
.then(addExclamation)
.then(function (greeting) {
console.log(greeting); // 'hello world!!!!’
}, function(error) {
console.error('uh oh: ', error); // 'uh oh: something bad happened’
});
โดยทั่วไปสัญญาเป็นวัตถุที่:
โดยพื้นฐานแล้วเนื่องจากการสนับสนุนข้อยกเว้นดั้งเดิมของภาษาไม่ทำงานเมื่อการคำนวณของคุณเกิดขึ้นในหลายเธรดการใช้งานตามสัญญาจะต้องมีกลไกการจัดการข้อผิดพลาดและสิ่งเหล่านี้กลายเป็นพระคล้ายกับประเภทMaybe
/ HaskellEither
ระบบประเภท Haskell จะกำหนดให้ผู้ใช้ต้องยอมรับความเป็นไปได้ของ a Nothing
ในขณะที่ภาษาการเขียนโปรแกรมมักไม่ต้องการข้อยกเว้น ซึ่งหมายความว่าเราจะทราบในเวลารวบรวมว่าผู้ใช้ได้ตรวจสอบข้อผิดพลาด
throws NPE
ลายเซ็นcatch(...) {throw ...}
ให้กับทุก ๆลายเซ็นและทุกส่วนของเมธอดเดียว แต่ฉันเชื่อว่ามีตลาดสำหรับการตรวจสอบในลักษณะเดียวกับอาจจะเป็น: ความถูกต้องเป็นตัวเลือกและติดตามในระบบประเภท
บางทีโมนาอาจเป็นแบบเดียวกับการตรวจสอบ "null หมายถึงข้อผิดพลาด" ของภาษากระแสหลักส่วนใหญ่ (ยกเว้นมันจะต้องตรวจสอบค่าว่าง) และมีข้อดีและข้อเสียเหมือนกัน
Maybe
ตัวเลขสองตัวโดยเขียนa + b
โดยไม่จำเป็นต้องตรวจสอบNone
และผลลัพธ์จะเป็นค่าที่เลือกได้อีกครั้ง
Maybe
ประเภทแต่การใช้Maybe
เป็น monad เพิ่มน้ำตาลไวยากรณ์ที่ช่วยให้การแสดงตรรกะ null-ish มากขึ้นอย่างหรูหรา
การจัดการข้อยกเว้นอาจเป็นความเจ็บปวดอย่างแท้จริงสำหรับแฟและการทดสอบ ฉันรู้ว่าหลามให้ "กับ" ไวยากรณ์ที่ดีที่ช่วยให้คุณสามารถดักจับข้อยกเว้นโดยไม่มีบล็อก "ลอง ... จับ" ที่เข้มงวด ตัวอย่างเช่นใน Java ลองใช้ catch catch block ที่มีขนาดใหญ่สำเร็จรูปทั้งแบบ verbose หรือ verbose ที่มากและยากที่จะสลาย ยิ่งไปกว่านั้น Java ยังเพิ่มเสียงรบกวนทั้งหมดที่มีการตรวจสอบและข้อยกเว้นที่ไม่ได้ตรวจสอบ
หากคุณจับข้อยกเว้นและปฏิบัติต่อพวกเขาเป็นคุณสมบัติของพื้นที่ monadic แทนการประมวลผลที่ผิดปกติคุณสามารถผสมและจับคู่ฟังก์ชันที่คุณผูกไว้ในพื้นที่นั้นโดยไม่คำนึงถึงสิ่งที่พวกเขาโยนหรือจับ
ถ้ายังดีกว่า monad ของคุณจะป้องกันเงื่อนไขที่อาจเกิดข้อยกเว้นได้ (เช่นการกดปุ่มเช็คอินเป็น null บางที) ก็ดีกว่า ถ้า ... ถ้าอย่างนั้นจะมากง่ายกว่าที่จะแยกตัวประกอบและทดสอบกว่าลอง ... จับ
จากสิ่งที่ฉันได้เห็น Go คือการใช้วิธีการที่คล้ายกันโดยระบุว่าแต่ละฟังก์ชั่นกลับมา (คำตอบข้อผิดพลาด) นั่นเป็นแบบเดียวกับ "การยก" ฟังก์ชั่นลงในช่องว่าง monad ที่ประเภทคำตอบหลักถูกตกแต่งด้วยข้อบ่งชี้ข้อผิดพลาดและข้อยกเว้นการขว้างปาและจับได้อย่างมีประสิทธิภาพ