ฉันสงสัยว่าอะไรคือข้อดีของMaybe monad ที่มีข้อยกเว้น? ดูเหมือนว่าMaybeเป็นวิธีการทางtry..catchไวยากรณ์ที่ชัดเจน (และค่อนข้างใช้พื้นที่มาก)
อัปเดตโปรดทราบว่าฉันตั้งใจจะไม่พูดถึง Haskell
ฉันสงสัยว่าอะไรคือข้อดีของMaybe monad ที่มีข้อยกเว้น? ดูเหมือนว่าMaybeเป็นวิธีการทางtry..catchไวยากรณ์ที่ชัดเจน (และค่อนข้างใช้พื้นที่มาก)
อัปเดตโปรดทราบว่าฉันตั้งใจจะไม่พูดถึง Haskell
คำตอบ:
การใช้Maybe(หรือลูกพี่ลูกน้องของมันEitherซึ่งใช้งานได้ในลักษณะเดียวกัน แต่ให้คุณคืนค่าโดยพลการแทนNothing) ทำหน้าที่แตกต่างกันเล็กน้อยกว่าข้อยกเว้น ในแง่ของจาวาก็เหมือนกับมีข้อยกเว้นตรวจสอบมากกว่าข้อยกเว้นรันไทม์ มันแสดงถึงสิ่งที่คาดหวังซึ่งคุณต้องจัดการแทนที่จะเป็นข้อผิดพลาดที่คุณไม่คาดคิด
ฟังก์ชันที่ต้องการindexOfคืนMaybeค่าเพราะคุณคาดหวังความเป็นไปได้ที่ไอเท็มนั้นจะไม่อยู่ในรายการ มันเหมือนกับกลับมาnullจากฟังก์ชั่นยกเว้นในวิธีที่ปลอดภัยประเภทที่บังคับให้คุณจัดการกับnullกรณีและปัญหา ทำงานในลักษณะเดียวกันยกเว้นว่าคุณสามารถส่งกลับข้อมูลที่เกี่ยวข้องกับกรณีข้อผิดพลาดดังนั้นจึงเป็นเรื่องจริงมากขึ้นคล้ายกับข้อยกเว้นกว่าEitherMaybe
ดังนั้นข้อดีของ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 คือการหลีกเลี่ยงสิ่งนี้ คุณสามารถเข้ารหัสซีแมนทิกส์การแพร่กระจายในฟังก์ชันผูกเพื่อให้คุณได้รับการตรวจสอบโมฆะ / ข้อผิดพลาดฟรี ครั้งเดียวที่คุณต้องตรวจสอบอย่างชัดเจนคือถ้าคุณต้องการส่งคืนสิ่งอื่นนอกเหนือจากNothinga 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 ที่ประเภทคำตอบหลักถูกตกแต่งด้วยข้อบ่งชี้ข้อผิดพลาดและข้อยกเว้นการขว้างปาและจับได้อย่างมีประสิทธิภาพ