คำอธิบายตามสัญชาตญาณของแนวคิดการเขียนโปรแกรมเชิงฟังก์ชันที่ซับซ้อนโดยไม่จำเป็น

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

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

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

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

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

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

นี่คือที่มาของอัจฉริยะของ Moggi ตอนที่ 1 เขาสังหรณ์ใจว่าค่านั้นแตกต่างในเชิงคุณภาพจากการคำนวณที่สร้างค่านั้น กล่าวอีกนัยหนึ่ง getc()เป็น การคำนวณที่สร้างchar และไม่ควรปะปนกับ char.

ดังนั้น แทนที่จะให้ getc() เป็นประเภท char ให้เราให้เป็นประเภท T[char] ซึ่งย่อมาจากการบอกว่ามันเป็นคอมพิวเตอร์Tation (หรือกระบวนการ) ที่สร้าง char ตอนแรกเหมือนจะถอยหลังไปหนึ่งก้าว ตอนนี้เราไม่สามารถใช้ getc()ในทุกตำแหน่งที่เราสามารถใช้ char ได้ ดังนั้น (getc() == ‘q’) จึงไม่ถูกต้องอีกต่อไป เนื่องจากประเภทที่ด้านใดด้านหนึ่งของ == นั้นแตกต่างกัน เราจะทำอย่างไรตอนนี้?

ฉันขอแนะนำให้คุณรู้จักกับคำคู่ใหม่: injection และ projection เมื่อเรานำค่าประเภท char มาฝังไว้ในประเภทอื่น เช่น T[char] ค่าดังกล่าวจะเรียกว่า injection ค่าผกผันของการฉีดเรียกว่า การฉายภาพ นี่คือที่ที่เราแยกค่าที่ฉีดออกจากการฝัง กล่าวอีกนัยหนึ่ง เราคาดการณ์ char จาก T[char] โดยทั่วไป การแทรกและการฉายภาพได้รับการออกแบบมาให้ผกผันซึ่งกันและกันเสมอ โดยประกอบขึ้นเป็นฟังก์ชันเอกลักษณ์

ตอนนี้ตัวดำเนินการฉายภาพจะยอมให้เรานำ char ออกจาก T[char] ได้หรือไม่ การฉายภาพนี้มีลักษณะอย่างไร? สำหรับกระบวนการคำนวณเช่น getc() เราจะ “ฉายภาพ จาก T[char] โดยรอให้การเรียก I/O ยุติ นำเสนอค่า จากนั้นจึงทำงานกับค่า โดยใช้มันเหมือนกับที่เราจะใช้อย่างอื่น ถ่านและค่าอื่น ๆ ที่ได้รับจากมัน อย่างไรก็ตาม เมื่อเราออกจากพื้นที่การคำนวณและที่ดินมูลค่าแล้ว ตอนนี้เราได้มองข้ามความจริงที่ว่า มีการคำนวณทั้งหมดที่สร้างมูลค่าและนำเรากลับมาที่จุดที่เราเริ่มต้น (กล่าวคือ ไม่สามารถยึดมั่นใน โลกภายนอกที่ getc() ลากเราเข้าไป )

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

let c = getc() in (c == ‘q’)

ขอผมทำลายเรื่องนี้สักหน่อย ในที่นี้ c มีประเภท char — ไม่ใช่ T[char]งานของ let ในที่นี้คือการฉายภาพ char จาก T[char] ผูกไว้กับชื่อ c และอนุญาตให้ใช้เป็น char ในร่างกาย (นิพจน์หลัง in) ในกรณีนี้ การฉายภาพหมายถึงการรอ getc() เพื่อส่งคืนอักขระ

ถ้า let กำลังดำเนินการฉายภาพมาตรฐาน นิพจน์นี้จะสร้างบูลีนที่ส่วนท้าย อย่างไรก็ตาม นิพจน์ทั้งหมดนี้ ยังคง มีการดำเนินการ I/O (เช่น การคำนวณ) ที่เกี่ยวข้องกับการสร้างบูลีนนี้ ซึ่งหมายความว่านิพจน์นี้ไม่ได้เป็นตัวแทนของบูลีนจริงๆ แต่เป็น การคำนวณ ที่สร้างบูลีน ดังนั้น สิ่งที่ถูกต้องที่ต้องทำคือกำหนดนิพจน์ let ทั้งหมดนี้ให้เป็นประเภท T[boolean] ซึ่งหมายความว่าเนื้อความของ let จะต้องส่งคืนประเภท T[boolean]

ในสูตรของ Moggi ประเภทนี้ จำเป็น สำหรับนิพจน์การฉายภาพ let ทั้งหมด และบังคับใช้โดยเครื่องตรวจสอบประเภท เพื่อให้แน่ใจว่าเราสามารถตอบสนองข้อกำหนดนี้ได้ เราจึงแนะนำตัวดำเนินการ η ซึ่งมีวัตถุประสงค์เพียงอย่างเดียวคือการแทรกค่าประเภท τ ลงใน Τ[τ] มันบังคับให้เราจำไว้ว่าค่านี้ถูกสร้างขึ้นจากการคำนวณ ดังนั้นเราจึงเขียน let ด้านบนใหม่ดังนี้:

let c = getc() in η(c == ‘q’)

ตอนนี้ ประเภทของนิพจน์นี้คือ T[boolean] โดยทั่วไป นี่หมายความว่าความจริงที่ว่าค่าที่ถูกสร้างขึ้นจากการคำนวณจะไม่มีวันลืม หากเราต้องใช้ค่าที่สร้างโดย let นี้ที่อื่น เราต้องจำไว้ว่ามันคือ T[boolean] หากต้องการแยกบูลีนนี้ เราจำเป็นต้องมี let อีกตัว และเนื้อหาของใครจะเป็น T[something] ไปเรื่อยๆ หากคุณเข้าใจย่อหน้านี้แล้วและต้องการอวดเพื่อนๆ ของคุณ ให้โยนคำว่า “กฎแห่งการเชื่อมโยงของพระสงฆ์” ไปทั่ว

เมื่อพิจารณาถึงข้อสรุปเชิงตรรกะแล้ว ในที่สุดโปรแกรมใดๆ ก็ตามที่ใช้การคำนวณจะสร้างคำตอบประเภท T[x] แทนที่จะเป็นค่าประเภท x เสมอ ความลับเล็กๆ น้อยๆ สกปรกของ monads ก็คือ มีการคาดการณ์ครั้งสุดท้ายที่เราต้องทำเพื่อดึง x ออกจาก T[x] จริงๆ ในทางปฏิบัติ T แต่ละตัวจะมาพร้อมกับการดำเนินการฉายภาพเสมอ แต่ต้องใช้เฉพาะในพื้นที่ที่ระบุอย่างชัดเจนเท่านั้น ซึ่งเราไม่ได้พยายามรักษาคุณสมบัติการคำนวณของ T[] อีกต่อไป

ประเภท T[] พร้อมด้วย η และ let คือ monad (บางครั้งก็มีชื่อเรียกอย่าง "Kleisli triple") ชื่อจริงมาจากโครงสร้างทฤษฎีหมวดหมู่ที่ทำให้แนวคิดนี้เป็นทางการในรูปแบบคณิตศาสตร์มากกว่าที่คนส่วนใหญ่จำเป็นต้องรู้ แค่นั้นแหละ. แม้ว่า T[] จะเป็นตัวยืนสำหรับการคำนวณที่เกี่ยวข้องกับ I/O ในกรณีของเรา แต่ก็สามารถยืนหยัดได้สำหรับการคำนวณประเภทต่างๆ มากมายที่เราจะเห็นด้านล่างนี้

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

ดังนั้นแม้ว่าฉันจะมี “เอฟเฟกต์” (ซึ่งเป็นชื่อที่เราตั้งให้กับการคำนวณเหล่านี้) ในโปรแกรมของฉัน แต่ฉันก็สามารถให้เหตุผลเกี่ยวกับโปรแกรมส่วนใหญ่ได้โดยไม่ต้องคำนึงถึงเอฟเฟกต์มากเกินไป เพราะตอนนี้พวกมันได้รับการบรรจุไว้อย่างดีใน T[] กฎของ let อย่าให้ฉันลืมเมื่อฉันจัดการกับการคำนวณ และเมื่อฉันจัดการกับค่าที่เกิดจากการคำนวณนั้น

ช่วงของการคำนวณที่สามารถปฏิบัติได้เช่นนี้มีขนาดใหญ่ I/O คือตัวอย่างหนึ่งข้างต้น ประเภทการคำนวณที่ใช้บ่อยอีกประเภทหนึ่งคือ "ความไม่แน่นอน" ในแง่ที่ว่าค่าอาจมีหรือไม่มีก็ได้ ตัวอย่างเช่น คุณอาจค้นหาคีย์ในตารางแฮชที่อาจพบหรือไม่พบ การคำนวณประเภทนี้รวมอยู่ใน monad Option วิธีคิดเกี่ยวกับเรื่องนี้ก็คือ หาก let พบว่าไม่มีอยู่จริงในขณะที่แยกค่าออกจากการคำนวณ (เช่น ตารางแฮชไม่มีคีย์ที่เรากำลังมองหา) let ทั้งหมดจะไม่มีอยู่จริง มิฉะนั้น เมื่อมีค่าอยู่ เนื้อความของ let ก็จะมีค่าเช่นกัน ใน Option การฉีดค่า (η) ทำได้โดยใช้ Some ใน Scala หรือ Just ใน Haskell ข้อมูลพื้นฐานอื่นๆ (เช่น get จากตารางแฮช ซึ่งในกรณีนี้จะคล้ายคลึงกับ getc) จะสร้างค่าใน Option โดยอัตโนมัติ

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

นี่เป็นวิธีที่สะอาดมากในการจัดโครงสร้างโปรแกรมที่มีเอฟเฟกต์ Moggi ใช้เพื่อกำหนดความหมายของเอฟเฟกต์ เช่น สถานะที่ไม่แน่นอน ความต่อเนื่อง ฯลฯ แต่ Phil Wadler เป็นผู้ "ขยาย" การใช้งานให้กับการเขียนโปรแกรมเชิงฟังก์ชัน ร่วมกับไซมอน เพย์ตัน โจนส์ และคนอื่นๆ เขาได้นำพวกเขามารวมตัวกันใน Haskell ต่อมาได้รวมเข้ากับภาษาการเขียนโปรแกรมอื่นๆ มากมาย รวมถึง Scala และ Swift

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

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

โครงสร้าง let … in … ที่หรูหรามากของ Moggi ได้รับการปรับปรุงใหม่ใน Scala (และ Swift) โดยมีชื่อว่า for … yield … ที่น่าเสียดาย และที่น่าเสียดายยิ่งกว่านั้นคือ do … return … ใน Haskell (พวกเขายังแตกต่างกันเล็กน้อยในความหมาย) โปรดสังเกตว่าทั้งสองภาษาเลือกที่จะตั้งชื่อการดำเนินการ binding เป็นการดำเนินการ จำเป็น ซึ่งเป็นการตัดสินใจออกแบบที่ฉันคิดว่าน่าเสียใจที่สุด ใครก็ตามที่อ่านโปรแกรมที่มีคำเหล่านั้นอยู่ในนั้นจะสับสนทันที เพราะแทนที่จะพาพวกเขาไปยังสถานที่ "ผูกมัด" มันจะพาพวกเขาไปยังสถานที่ "วนซ้ำ" หรือแม้กระทั่ง "กลับมา" อย่างน่าสับสนและหลีกเลี่ยงไม่ได้จากที่ไหนสักแห่ง

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

ใน "บทความวิจัย" ฉบับถัดมา แวดเลอร์สังเกตเห็นและใช้ประโยชน์จากความจริงที่ว่ารายการและ monads มีคุณสมบัติทางพีชคณิตที่คล้ายคลึงกัน และความคล้ายคลึงนี้สามารถใช้ประโยชน์ได้โดยการขยายฟังก์ชันรายการบางอย่างไปยัง monads ทั้งหมด โดยทั่วไป ตัวอย่างเช่น เรามีฟังก์ชันรายการ map ที่รับฟังก์ชันประเภท (a ->b)และแมปกับ List[a] เพื่อสร้าง List[b] Wadler ตั้งข้อสังเกตว่าความคล้ายคลึงนี้สามารถนำไปใช้ประโยชน์สำหรับพระสงฆ์ได้ ยกเว้นว่าแทนที่จะเป็น List[a]และ List[b] คุณจะมีประเภท T[a] และ T[b] ดังนั้น สำหรับ monad ประเภท T ใดๆ เราสามารถกำหนดได้:

map (f: (a->b), m:T[a]) : T[b] = let v = m in η(f v)

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

ฉันรู้ว่า Future คืออะไร และฉันรู้ว่า flatMap คืออะไร แต่ flatMapping ถึง Future? ฮะ? สิ่งนั้นหมายความว่าอย่างไร กันแน่? น่าเสียดายที่ในบางภาษา (เช่น Scala และ Swift) จริงๆ แล้ว ไม่ หมายถึงอะไรบางอย่าง มันเป็นชื่อของบางสิ่งที่ในความเป็นจริงแล้วเป็นส่วนหนึ่งของคำจำกัดความดั้งเดิมของmonads มันคือตัวดำเนินการ bind ซึ่ง let แบ่งออกเป็น: let v = Exp_0 ใน Exp_1 แปลเป็น bind(Exp_0, λv. Exp_1) ในทางคณิตศาสตร์เขียนแทนด้วย ‘*’ และ Haskell เรียกมันว่า >>=

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

ในทำนองเดียวกัน for (เช่น การวนซ้ำ) เหนือรายการที่เป็นความเข้าใจที่สมเหตุสมผลในโลกของรายการ (ในท้ายที่สุดแล้วบน Set comprehensions ที่พบได้ทั่วไปในทฤษฎีเซต) กลายเป็นการวนซ้ำที่ไม่มีความหมายโดยสิ้นเชิงเหนือ monad (เช่น อนุญาตให้ฉันวนซ้ำคุณมากกว่า Option.หือ?)

น่าหงุดหงิดไม่แพ้กัน ไม่ใช่ว่า list function ทั้งหมดจะขยายไปถึง monads ตัวอย่างเช่น ฉันค่อนข้างจะเห็นว่า head ของ Future อาจเป็นเท่าใด แต่ tail ของ Future คืออะไรล่ะ? การคำนวณที่เหลือ หลังจาก การคำนวณสิ้นสุดลงหรืออาจเป็นการคำนวณที่ เป็นเพื่อให้ฉันสามารถรันได้อีกครั้ง ใช่ใช่ใช่. ฉันรู้ว่าพีชคณิตนั้นคล้ายกันมากและได้ผลในทางคณิตศาสตร์ แต่โปรแกรมมีไว้สำหรับการอ่าน และความตั้งใจเป็นสิ่งสำคัญ โลกใดก็ตามที่ flatMap เหมือนกับ bindคือโลกที่ผิดพลาดอย่างมหันต์

ทำไมไม่ยึดติดกับการเข้าเล่ม let ที่เรียบง่ายและสง่างามมันยากขนาดนั้นเลยเหรอ? นีโอไฟต์หลายรุ่นต้องดิ้นรน (และอาจยังคงดิ้นรน) ด้วยการแยกการคำนวณและค่าต่างๆ ออกไปอย่างง่ายๆ เนื่องจากเครื่องมือที่ได้รับการออกแบบมากเกินไป ใช่ let มีความหมายอื่นๆ ในภาษาอื่นๆ ส่วนใหญ่ แต่ปัญหาดังกล่าวได้รับการแก้ไขอย่างง่ายดายด้วยไวยากรณ์เล็กๆ น้อยๆ เช่น mlet, let[T] (เช่น let[Option] c = … in …) หรือบางอย่างที่มีความหมายพอๆ กัน หรือมีความเสี่ยงที่จะมีความซับซ้อนมากขึ้นเล็กน้อย ให้ใช้ตัวดำเนินการ bind ที่ทำสิ่งเดียวกันได้สำเร็จโดยไม่ต้องใช้ไวยากรณ์

ดังนั้น หากคุณยังใหม่ต่ออารามในภาษาใดก็ตามที่คุณทำงานอยู่ ให้ค้นหาค่าที่เทียบเท่ากับ T[]ที่คุณสนใจ ระบุการคำนวณที่มันเป็นตัวแทน และค่าที่เทียบเท่ากับ let … in … และนั่นคือ ทั้งหมดที่คุณต้องการ อย่างอื่นมีเสียงรบกวน