Monad ในภาษาอังกฤษธรรมดา? (สำหรับโปรแกรมเมอร์ OOP ที่ไม่มีพื้นหลัง FP)


743

ในแง่ที่โปรแกรมเมอร์ OOP จะเข้าใจ (ไม่มีพื้นหลังการเขียนโปรแกรมการทำงาน) monad คืออะไร?

แก้ปัญหาอะไรได้บ้างและสถานที่ที่ใช้บ่อยที่สุดคืออะไร

แก้ไข:

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


10
โพสต์บล็อกนี้ดีมาก: blog.sigfpe.com/2006/08/you-could-have-invented-monads-and.html
Pascal Cuoq


10
@Pavel: คำตอบที่เราได้มีดังต่อจากเอริคเป็นมากดีกว่าคนในบรรดาข้อเสนอแนะอื่น ๆ ของคิวสำหรับคนที่มีพื้นหลัง OO (เมื่อเทียบกับพื้นหลัง FP ก)
Donal Fellows

5
@ Donal: หากนี่เป็นผู้ล่อลวง (ซึ่งฉันไม่มีความเห็น) ควรเพิ่มคำตอบที่ดีให้กับต้นฉบับ นั่นคือ: คำตอบที่ดีไม่ได้ขัดขวางการปิดซ้ำซ้อน ถ้ามันใกล้เคียงกันมากพอสิ่งนี้สามารถทำได้โดยผู้ดำเนินรายการเป็นการรวม
dmckee --- ผู้ดูแลอดีตลูกแมว

3
ดูเพิ่มเติม: stackoverflow.com/questions/674855/...

คำตอบ:


732

UPDATE: คำถามนี้เป็นหัวข้อของซีรีย์บล็อกที่มีความยาวมากซึ่งคุณสามารถอ่านได้ที่Monads - ขอบคุณสำหรับคำถามที่ยอดเยี่ยม!

ในแง่ที่โปรแกรมเมอร์ OOP จะเข้าใจ (ไม่มีพื้นหลังการเขียนโปรแกรมการทำงาน) monad คืออะไร?

monad เป็น"แอมป์" ประเภทที่เชื่อฟังกฎระเบียบบางอย่างและที่มีการดำเนินการบางอย่างที่มีให้

ครั้งแรก "เครื่องขยายเสียงประเภท" คืออะไร? โดยที่ฉันหมายถึงระบบบางอย่างที่ช่วยให้คุณใช้ประเภทและเปลี่ยนเป็นประเภทพิเศษเพิ่มเติม ยกตัวอย่างเช่นใน C # Nullable<T>พิจารณา นี่คือเครื่องขยายเสียงประเภท มันช่วยให้คุณใช้ประเภทพูดintและเพิ่มความสามารถใหม่ให้กับประเภทนั้นกล่าวคือตอนนี้มันอาจเป็นโมฆะเมื่อไม่สามารถทำได้มาก่อน

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

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

int M(int x) { return x + N(x * 2); }

จากนั้นฟังก์ชั่นที่สอดคล้องกันบนNullable<int>สามารถทำให้ผู้ประกอบการทั้งหมดและการโทรในนั้นทำงานร่วมกัน "ในลักษณะเดียวกัน" ที่พวกเขาเคยทำมาก่อน

(นั่นเป็นสิ่งที่คลุมเครือและไม่แน่นอนอย่างไม่น่าเชื่อคุณขอคำอธิบายที่ไม่ได้คาดเดาอะไรเกี่ยวกับความรู้เกี่ยวกับองค์ประกอบการทำงาน)

"การดำเนินงาน" คืออะไร?

  1. มีการดำเนินการ "หน่วย" (บางครั้งเรียกว่าการดำเนินการ "คืน") ที่รับค่าจากชนิดธรรมดาและสร้างค่า monadic ที่เทียบเท่า ในสาระสำคัญนี้มีวิธีที่จะใช้มูลค่าของประเภทที่ไม่ได้ดัดแปลงและเปลี่ยนเป็นค่าของชนิดที่ถูกขยาย มันสามารถนำมาใช้เป็นตัวสร้างในภาษา OO

  2. มีการดำเนินการ "ผูก" ที่รับค่า monadic และฟังก์ชันที่สามารถแปลงค่าและส่งคืนค่า monadic ใหม่ Bind คือการดำเนินการที่สำคัญที่กำหนดความหมายของ monad มันช่วยให้เราสามารถแปลงการดำเนินงานในประเภทที่ไม่ได้รับการดัดแปลงเป็นการดำเนินการกับประเภทที่ขยายซึ่งปฏิบัติตามกฎขององค์ประกอบการทำงานที่กล่าวถึงก่อนหน้านี้

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

อีกครั้งNullable<T>เป็นตัวอย่าง คุณสามารถเปลี่ยนเป็นinta Nullable<int>ด้วยตัวสร้าง คอมไพเลอร์ C # จะดูแล "การยก" ที่เป็นโมฆะที่สุดสำหรับคุณ แต่ถ้าไม่ทำเช่นนั้น

int M(int x) { whatever }

ถูกแปลงเป็น

Nullable<int> M(Nullable<int> x) 
{ 
    if (x == null) 
        return null; 
    else 
        return new Nullable<int>(whatever);
}

และการเปลี่ยนNullable<int>กลับเข้าไปintเป็นเสร็จแล้วด้วยValueคุณสมบัติ

มันคือการแปลงฟังก์ชั่นที่เป็นบิตที่สำคัญ ขอให้สังเกตว่าซีแมนติกส์ที่เกิดขึ้นจริงของการดำเนินการ nullable - ว่าการดำเนินงานในการnullเผยแพร่null- ถูกจับในการเปลี่ยนแปลง เราสามารถพูดคุยเรื่องนี้

สมมติว่าคุณมีฟังก์ชั่นจากintไปเช่นเดิมของเราint Mคุณสามารถทำให้มันกลายเป็นฟังก์ชั่นที่รับintและส่งกลับ a Nullable<int>เพราะคุณสามารถเรียกใช้ผลลัพธ์ผ่านตัวสร้าง nullable ตอนนี้สมมติว่าคุณมีวิธีการสั่งซื้อที่สูงกว่านี้:

static Nullable<T> Bind<T>(Nullable<T> amplified, Func<T, Nullable<T>> func)
{
    if (amplified == null) 
        return null;
    else
        return func(amplified.Value);
}

ดูว่าคุณสามารถทำอะไรกับสิ่งนั้น วิธีการใด ๆ ที่จะใช้เวลาintและการส่งกลับintหรือจะใช้เวลาintและผลตอบแทนNullable<int>ในขณะนี้สามารถมีความหมาย nullable ใช้กับมัน

นอกจากนี้: สมมติว่าคุณมีสองวิธี

Nullable<int> X(int q) { ... }
Nullable<int> Y(int r) { ... }

และคุณต้องการเขียนมัน:

Nullable<int> Z(int s) { return X(Y(s)); }

นั่นคือZเป็นองค์ประกอบของและX Yแต่คุณไม่สามารถทำเช่นนั้นเพราะXจะใช้เวลาintและผลตอบแทนY Nullable<int>แต่เนื่องจากคุณมีการดำเนินการ "ผูก" คุณสามารถทำงานนี้:

Nullable<int> Z(int s) { return Bind(Y(s), X); }

การดำเนินการผูกบน monad คือสิ่งที่ทำให้องค์ประกอบของฟังก์ชั่นในประเภทขยายทำงาน "กฏ" ที่ฉัน handwaved เกี่ยวกับข้างต้นเป็นที่ monad รักษากฎขององค์ประกอบการทำงานปกติ; การแต่งเพลงด้วยฟังก์ชั่นประจำตัวนั้นส่งผลให้เกิดฟังก์ชั่นดั้งเดิมการแต่งเพลงนั้นเชื่อมโยงกันและอื่น ๆ

ใน C # "Bind" เรียกว่า "SelectMany" ดูวิธีการทำงานของ Monad เราต้องมีสองสิ่ง: เปลี่ยนค่าเป็นลำดับและผูกการดำเนินการในลำดับ ในฐานะโบนัสเรายังมี "เปลี่ยนลำดับกลับเป็นค่า" การดำเนินงานเหล่านั้นคือ:

static IEnumerable<T> MakeSequence<T>(T item)
{
    yield return item;
}
// Extract a value
static T First<T>(IEnumerable<T> sequence)
{
    // let's just take the first one
    foreach(T item in sequence) return item; 
    throw new Exception("No first item");
}
// "Bind" is called "SelectMany"
static IEnumerable<T> SelectMany<T>(IEnumerable<T> seq, Func<T, IEnumerable<T>> func)
{
    foreach(T item in seq)
        foreach(T result in func(item))
            yield return result;            
}

กฎ monad ที่ nullable คือ "เพื่อรวมสองฟังก์ชันที่สร้าง nullables ร่วมกันตรวจสอบเพื่อดูว่าหนึ่งในผลลัพธ์เป็นโมฆะถ้ามันผลิต null ถ้ามันไม่ได้แล้วเรียกด้านนอกกับผล" นั่นคือความหมายที่ต้องการเป็นโมฆะ

กฎ Monad ลำดับคือ "เพื่อรวมสองฟังก์ชันที่สร้างลำดับด้วยกันใช้ฟังก์ชันภายนอกกับทุกองค์ประกอบที่ผลิตโดยฟังก์ชั่นด้านในและจากนั้นเชื่อมต่อลำดับผลลัพธ์ทั้งหมดเข้าด้วยกัน" ความหมายพื้นฐานของพระถูกจับในBind/ SelectManyวิธี; นี่เป็นวิธีการที่จะบอกคุณว่า monad จริงๆหมายถึง

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

static IEnumerable<U> SelectMany<T,U>(IEnumerable<T> seq, Func<T, IEnumerable<U>> func)
{
    foreach(T item in seq)
        foreach(U result in func(item))
            yield return result;            
}

ตอนนี้เราสามารถพูดว่า "ขยายจำนวนเต็มของแต่ละกลุ่มนี้เป็นลำดับของจำนวนเต็มเปลี่ยนจำนวนเต็มนี้เป็นกลุ่มของสตริงขยายเป็นลำดับของสตริงตอนนี้ใส่การดำเนินการทั้งสองเข้าด้วยกัน: ขยายกลุ่มของจำนวนเต็มนี้เข้าด้วยกัน ลำดับทั้งหมดของสตริง " Monads อนุญาตให้คุณเขียนแอมป์ของคุณ

แก้ปัญหาอะไรได้บ้างและสถานที่ที่ใช้บ่อยที่สุดคืออะไร

มันค่อนข้างเหมือนกับการถามว่า "รูปแบบของซิงเกิลตันแก้ปัญหาอะไรได้บ้าง" แต่ฉันจะให้ช็อตมัน

โดยทั่วไป Monads จะใช้ในการแก้ปัญหาเช่น:

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

C # ใช้ monads ในการออกแบบ ตามที่กล่าวมาแล้วรูปแบบที่ไม่มีค่าใช้จ่ายนั้นคล้ายกับ "บางที monad" อย่างมาก LINQ สร้างขึ้นจากพระทั้งหมด SelectManyวิธีคือสิ่งที่จะทำงานความหมายขององค์ประกอบของการดำเนินงาน (Erik Meijer ชอบที่จะชี้ให้เห็นว่าทุกฟังก์ชั่น LINQ สามารถนำไปใช้ได้จริงโดยSelectMany; ทุกอย่างเป็นเพียงความสะดวกสบาย)

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

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

จุดเริ่มต้นที่ดีคือวิธีที่เราใช้งาน LINQ ใน C # ศึกษาSelectManyวิธีการ มันเป็นกุญแจสำคัญในการทำความเข้าใจวิธีการทำงานของ Monad ใน C # มันเป็นวิธีที่ง่ายมาก แต่ทรงพลังมาก!


แนะนำให้อ่านเพิ่มเติม:

  1. สำหรับคำอธิบายที่ลึกซึ้งยิ่งขึ้นและในเชิงทฤษฎีเกี่ยวกับพระใน C # ฉันขอแนะนำบทความของ Wes Dyer เพื่อนร่วมงานของEric Lippert บทความนี้เป็นสิ่งที่อธิบายให้ฉัน monads ในที่สุดเมื่อพวกเขา "คลิก" สำหรับฉัน
  2. ภาพประกอบที่ดีของทำไมคุณอาจต้องการ monad รอบ(ใช้ Haskell ในตัวอย่างของมัน)
  3. เรียงจาก "แปล" ของบทความก่อนหน้าเป็น JavaScript


17
นี่เป็นคำตอบที่ดี แต่หัวฉันก็พูดพลัน ฉันจะติดตามและมองไปที่มันในสุดสัปดาห์นี้และถามคำถามคุณหากสิ่งต่าง ๆ ไม่สงบและมีเหตุผลในหัวของฉัน
พอลนาธาน

5
คำอธิบายที่ยอดเยี่ยมตามปกติเอริค สำหรับการอภิปรายเชิงทฤษฎี (แต่ยังคงน่าสนใจมาก) ฉันได้พบโพสต์บล็อกของ Bart De Smet ใน MinLINQ ซึ่งเป็นประโยชน์ในการเขียนโปรแกรมเชิงหน้าที่บางส่วนที่สร้างขึ้นกลับไปที่ C # เช่นกัน community.bartdesmet.net/blogs/bart/archive/2010/01/01/…
Ron Warholic

41
มันทำให้รู้สึกมากขึ้นกับผมที่จะบอกว่ามันaugmentsประเภทมากกว่าamplifiesพวกเขา
Gabe

61
@slomojo: และฉันเปลี่ยนมันกลับเป็นสิ่งที่ฉันเขียนและตั้งใจจะเขียน หากคุณและ Gabe ต้องการเขียนคำตอบของคุณเอง
Eric Lippert

24
@Eric ขึ้นอยู่กับคุณแน่นอน แต่แอมป์หมายความว่ามีการเพิ่มคุณสมบัติที่มีอยู่ซึ่งทำให้เข้าใจผิด
ocodo

341

ทำไมเราต้องการพระ

  1. เราต้องการตั้งโปรแกรมโดยใช้ฟังก์ชั่นเท่านั้น ("ฟังก์ชั่นการเขียนโปรแกรม" หลังจากทั้งหมด -FP)
  2. จากนั้นเรามีปัญหาใหญ่ครั้งแรก นี่คือโปรแกรม:

    f(x) = 2 * x

    g(x,y) = x / y

    เราจะบอกได้อย่างไรว่า อะไรที่ต้องถูกประหารชีวิตก่อน ? เราจะสร้างลำดับของฟังก์ชั่นที่สั่ง (เช่นโปรแกรม ) โดยใช้ไม่เกินฟังก์ชั่นได้อย่างไร?

    การแก้ไข: ฟังก์ชั่นการเขียน ถ้าคุณต้องการที่แรกgและจากนั้นเพียงแค่เขียนf f(g(x,y))ได้. แต่ ...

  3. ปัญหาเพิ่มเติม: ฟังก์ชั่นบางอย่างอาจล้มเหลว (เช่นg(2,0)หารด้วย 0) เรามีไม่ "ข้อยกเว้น" ใน FP เราจะแก้ปัญหาได้อย่างไร

    วิธีแก้ปัญหา: อนุญาตให้ฟังก์ชั่นคืนสองชนิด : แทนที่จะมีg : Real,Real -> Real(ฟังก์ชั่นจากสอง reals เป็นของจริง), อนุญาตให้g : Real,Real -> Real | Nothing(ฟังก์ชั่นจากสอง reals เป็น (จริงหรือไม่มีอะไร))

  4. แต่ฟังก์ชั่นควร (จะง่าย) ผลตอบแทนเพียงสิ่งหนึ่งที่

    วิธีการแก้ไข: เรามาสร้างข้อมูลประเภทใหม่ที่จะส่งคืน " มวยชนิด " ที่ล้อมรอบอาจเป็นของจริงหรือไม่มีอะไรเลย g : Real,Real -> Maybe Realดังนั้นเราสามารถมี ได้. แต่ ...

  5. สิ่งที่เกิดขึ้นในขณะนี้เพื่อf(g(x,y))? ไม่พร้อมที่จะกินf Maybe Realและเราไม่ต้องการที่จะเปลี่ยนฟังก์ชั่นที่เราสามารถเชื่อมต่อกับทุกการกินgMaybe Real

    การแก้ไข: ขอมีฟังก์ชั่นพิเศษเพื่อ "การเชื่อมต่อ" / "เขียน" / "ลิงก์" ฟังก์ชั่น ด้วยวิธีนี้เราสามารถปรับเอาท์พุทของฟังก์ชั่นหนึ่งเพื่อเลี้ยงสิ่งต่อไปนี้

    ในกรณีของเรา: g >>= f(เชื่อมต่อ / เขียนgถึงf) เราต้องการ>>=ที่จะได้รับgของที่ส่งออกตรวจสอบได้และในกรณีที่มันเป็นNothingเพียงแค่ไม่ได้โทรfและผลตอบแทนNothing; หรือในทางกลับกันให้แยกกล่องบรรจุRealแล้วป้อนเข้าfด้วย (อัลกอริทึมนี้เป็นเพียงการนำไปปฏิบัติ>>=สำหรับMaybeประเภท)

  6. ปัญหาอื่น ๆ อีกมากมายเกิดขึ้นซึ่งสามารถแก้ไขได้โดยใช้รูปแบบเดียวกันนี้: 1. ใช้ "กล่อง" เพื่อประมวล / เก็บความหมาย / ค่าต่าง ๆ และมีฟังก์ชั่นเช่นgนั้นคืนค่าเหล่านั้น "ค่ากล่อง" 2. มีผู้แต่ง / ผู้เชื่อมโยงg >>= fเพื่อช่วยในการเชื่อมต่อgเอาท์พุทfของอินพุตดังนั้นเราจึงไม่จำเป็นต้องเปลี่ยนfเลย

  7. ปัญหาที่น่าสังเกตที่สามารถแก้ไขได้โดยใช้เทคนิคนี้คือ:

    • มีรัฐระดับโลกที่ทุกฟังก์ชั่นในลำดับของฟังก์ชั่น ( "โปรแกรม") สามารถแบ่งปัน: StateMonadวิธีการแก้ปัญหา

    • เราไม่ชอบ "ฟังก์ชั่นที่ไม่บริสุทธิ์": ฟังก์ชั่นที่ให้ผลลัพธ์ที่แตกต่างกันสำหรับอินพุตเดียวกัน ดังนั้นให้ทำเครื่องหมายฟังก์ชั่นเหล่านั้นทำให้พวกเขากลับค่าที่ติดแท็ก / กล่อง: IOmonad

ความสุขทั้งหมด !!!!


2
@DmitriZaitsev ข้อยกเว้นสามารถเกิดขึ้นได้ใน "รหัสไม่บริสุทธิ์" (IO monad) เท่าที่ฉันรู้
cibercitizen1 1

3
@DmitriZaitsev บทบาทของ Nothing สามารถเล่นได้กับเกมประเภทอื่น (แตกต่างจาก Real ที่คาดไว้) นั่นไม่ใช่ประเด็น ในตัวอย่างเรื่องคือวิธีการปรับฟังก์ชั่นในสายโซ่เมื่อคนก่อนหน้านี้อาจกลับมาเป็นประเภทค่าที่ไม่คาดคิดไปยังประเภทต่อไปนี้โดยไม่ต้องผูกมัดหลัง (เฉพาะการยอมรับจริงเป็นอินพุต)
cibercitizen1

3
อีกจุดที่สับสนคือคำว่า "monad" ปรากฏขึ้นเพียงสองครั้งในคำตอบของคุณและเมื่อรวมกับคำอื่น ๆ - StateและIOโดยที่ไม่มีสิ่งใดเลยรวมถึงความหมายที่แท้จริงของ "monad" ที่ได้รับ
Dmitri Zaitsev

31
สำหรับฉันในฐานะคนที่มาจากภูมิหลังของ OOP คำตอบนี้อธิบายได้ดีจริงๆแรงจูงใจเบื้องหลังการมี monad และสิ่งที่ monad จริง ๆ (อีกมากที่คำตอบที่ยอมรับ) ดังนั้นฉันคิดว่ามันมีประโยชน์มาก ขอบคุณมาก @ cibercitizen1 และ +1
akhilless

3
ฉันได้อ่านเกี่ยวกับการเขียนโปรแกรมฟังก์ชั่นปิดและเปิดประมาณหนึ่งปี คำตอบนี้และโดยเฉพาะอย่างยิ่งสองจุดแรกในที่สุดก็ทำให้ฉันเข้าใจว่าการเขียนโปรแกรมที่จำเป็นจริงๆหมายถึงอะไรและทำไมการเขียนโปรแกรมการทำงานแตกต่างกัน ขอบคุณ!
jrahhali

82

ฉันจะบอกว่าการเปรียบเทียบ OO ที่ใกล้เคียงที่สุดกับ monads คือ " รูปแบบคำสั่ง "

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

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

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

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

ในความเป็นจริงรูปแบบคำสั่งไม่ได้ถูกใช้เป็นคุณสมบัติภาษาทั่วไปเช่นนี้ ค่าโสหุ้ยในการเปลี่ยนแต่ละคำสั่งให้อยู่ในคลาสที่แยกต่างหากจะนำไปสู่จำนวนรหัสสำเร็จรูปที่ไม่สามารถทนทานได้ แต่โดยหลักการแล้วมันสามารถใช้ในการแก้ปัญหาเช่นเดียวกับ monads ที่ใช้ในการแก้ปัญหาใน fp


15
ฉันเชื่อว่านี่เป็นคำอธิบาย monad แรกที่ฉันเห็นว่าไม่ได้อาศัยแนวคิดการเขียนโปรแกรมที่ใช้งานได้และวางไว้ในเงื่อนไข OOP จริง คำตอบที่ดีจริงๆ
David K. Hess

นี่คือสิ่งที่ใกล้เคียงที่สุด 2 สิ่งที่พระสงฆ์อยู่ใน FP / Haskell ยกเว้นว่าคำสั่งวัตถุ "รู้" ซึ่งพวกเขาเป็น "ตรรกะการภาวนา" พวกเขาอยู่ (และมีเพียงคนที่เข้ากันได้จะถูกล่ามโซ่ไว้ด้วยกัน); ผู้เรียกใช้ให้ค่าแรก มันไม่เหมือนคำสั่ง "พิมพ์" ที่สามารถดำเนินการได้โดย "ตรรกะการดำเนินการที่ไม่ได้กำหนด" ไม่ต้องเป็น "I / O ลอจิก" (เช่น IO monad) แต่นอกจากนั้นมันอยู่ใกล้มาก คุณสามารถพูดได้ว่าMonads เป็นเพียงโปรแกรม (สร้างจาก Statement Code เพื่อดำเนินการในภายหลัง) ในวันแรก "ผูก" ถูกพูดถึงว่าเป็น"อัฒภาค" ที่ตั้งโปรแกรมได้
Will Ness

1
@ DavidK.Hess ฉันสงสัยอย่างไม่น่าเชื่อของคำตอบที่ใช้ FP เพื่ออธิบายแนวคิดพื้นฐานของ FP และโดยเฉพาะคำตอบที่ใช้ภาษา FP เช่น Scala ทำได้ดีมาก JacquesB!
Reinstate Monica

62

ในแง่ที่โปรแกรมเมอร์ OOP จะเข้าใจ (ไม่มีพื้นหลังการเขียนโปรแกรมการทำงาน) monad คืออะไร?

แก้ปัญหาอะไรได้บ้างและสถานที่ที่ใช้บ่อยที่สุดคือสถานที่ที่ใช้บ่อยที่สุด

ในแง่ของการเขียนโปรแกรม OO monad คืออินเทอร์เฟซ (หรือน่าจะเป็นมิกซ์อิน) ปรับพารามิเตอร์ตามประเภทด้วยสองวิธีreturnและbindที่อธิบาย:

  • วิธีการฉีดค่าเพื่อรับค่า monadic ของชนิดค่าที่ถูกฉีดนั้น
  • วิธีการใช้ฟังก์ชั่นที่ทำให้ค่า monadic จาก non-monadic บนค่า monadic

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

โดยเฉพาะอย่างยิ่งMonad"อินเทอร์เฟซ" จะคล้ายกับIEnumeratorหรือIIteratorว่าใช้ชนิดที่ตัวเองใช้ชนิด Monadแม้ว่าจุดหลักของความสามารถในการเชื่อมต่อการดำเนินงานตามประเภทการตกแต่งภายในถึงจุดที่มี "ชนิดภายใน" ใหม่ในขณะที่รักษา - หรือแม้กระทั่งการเสริม - โครงสร้างข้อมูลของชั้นเรียนหลัก


1
returnจริง ๆ แล้วจะไม่เป็นวิธีการใน Monad เพราะมันไม่ได้ใช้อินสแตนซ์ Monad เป็นข้อโต้แย้ง (เช่น: ไม่มีสิ่งนี้ / ตนเอง)
Laurence Gonsalves

@ LaurenceGonsalves: เนื่องจากฉันกำลังมองหาสิ่งนี้สำหรับวิทยานิพนธ์ปริญญาตรีของฉันฉันคิดว่าสิ่งที่เป็นข้อ จำกัด ส่วนใหญ่คือการขาดวิธีการคงที่ในส่วนต่อประสานใน C # / Java คุณสามารถหาทางนำเรื่องราวของ Monad ไปใช้อย่างน้อยก็ต้องมีการผูกมัดแบบคงที่แทนที่จะเป็นตามประเภทของคลาส ที่น่าสนใจนี่อาจใช้ได้แม้จะไม่มีประเภทที่สูงขึ้น
Sebastian Graf

42

คุณมีงานนำเสนอเมื่อไม่นานมานี้ " Monadologie - ความช่วยเหลือระดับมืออาชีพเกี่ยวกับประเภทความวิตกกังวล " โดยChristopher League (12 กรกฎาคม 2010) ซึ่งค่อนข้างน่าสนใจสำหรับหัวข้อเรื่องความต่อเนื่องและ Monad
วิดีโอไปด้วยนี้ (SlideShare) นำเสนอเป็นจริงที่มีอยู่ใน Vimeo
ส่วน Monad เริ่มต้นในประมาณ 37 นาทีในวิดีโอหนึ่งชั่วโมงนี้และเริ่มต้นด้วยการนำเสนอภาพนิ่ง 42 จาก 58 ในการนำเสนอภาพนิ่ง 58 รายการ

มันถูกนำเสนอเป็น "รูปแบบการออกแบบชั้นนำสำหรับการเขียนโปรแกรมฟังก์ชั่น" แต่ภาษาที่ใช้ในตัวอย่างคือ Scala ซึ่งเป็นทั้ง OOP และการทำงาน
คุณสามารถอ่านเพิ่มเติมเกี่ยวกับ Monad ใน Scala ได้ในบล็อกโพสต์ " Monads - อีกวิธีหนึ่งในการคำนวณนามธรรมใน Scala " จากDebasish Ghosh (27 มีนาคม 2008)

คอนสตรัคเตอร์ประเภทM เป็น monad ถ้าสนับสนุนการดำเนินการเหล่านี้:

# the return function
def unit[A] (x: A): M[A]

# called "bind" in Haskell 
def flatMap[A,B] (m: M[A]) (f: A => M[B]): M[B]

# Other two can be written in term of the first two:

def map[A,B] (m: M[A]) (f: A => B): M[B] =
  flatMap(m){ x => unit(f(x)) }

def andThen[A,B] (ma: M[A]) (mb: M[B]): M[B] =
  flatMap(ma){ x => mb }

ตัวอย่างเช่น (ใน Scala):

  • Option เป็น monad
    def unit [A] (x: A): ตัวเลือก [A] = บางส่วน (x)

    def flatMap [A, B] (m: ตัวเลือก [A]) (f: A => ตัวเลือก [B]): ตัวเลือก [B] =
      การแข่งขัน m
       กรณีไม่มี => ไม่มี
       กรณีบาง (x) => f (x)
      }
  • List คือ Monad
    def unit [A] (x: A): List [A] = List (x)

    def flatMap [A, B] (m: รายการ [A]) (f: A => รายการ [B]): List [B] =
      การแข่งขัน m
        กรณี Nil => ไม่มี
        กรณี x :: xs => f (x) ::: flatMap (xs) (f)
      }

Monad เป็นเรื่องใหญ่ใน Scala เนื่องจากไวยากรณ์ที่สะดวกสบายที่สร้างขึ้นเพื่อใช้ประโยชน์จากโครงสร้าง Monad:

forความเข้าใจใน Scala :

for {
  i <- 1 to 4
  j <- 1 to i
  k <- 1 to j
} yield i*j*k

แปลโดยคอมไพเลอร์เป็น:

(1 to 4).flatMap { i =>
  (1 to i).flatMap { j =>
    (1 to j).map { k =>
      i*j*k }}}

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

ในตัวอย่างข้างต้น flatMap ใช้เวลาเป็น input ปิดและผลตอบแทน(SomeType) => List[AnotherType] List[AnotherType]จุดสำคัญที่ควรทราบคือ flatMaps ทั้งหมดใช้ประเภทการปิดเดียวกันกับอินพุตและส่งคืนชนิดเดียวกับเอาต์พุต

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


หากคุณดำเนินการสองอย่าง (ที่อาจล้มเหลว) และส่งผลลัพธ์ไปยังอันดับที่สามเช่น:

lookupVenue: String => Option[Venue]
getLoggedInUser: SessionID => Option[User]
reserveTable: (Venue, User) => Option[ConfNo]

แต่หากไม่มีการใช้ประโยชน์จาก Monad คุณจะได้รับรหัส OOP ที่ซับซ้อนเช่น:

val user = getLoggedInUser(session)
val confirm =
  if(!user.isDefined) None
  else lookupVenue(name) match {
    case None => None
    case Some(venue) =>
      val confno = reserveTable(venue, user.get)
      if(confno.isDefined)
        mailTo(confno.get, user.get)
      confno
  }

ในขณะที่ Monad คุณสามารถทำงานกับประเภทจริง ( Venue, User) เช่นเดียวกับการดำเนินงานทั้งหมดและเก็บข้อมูลการตรวจสอบตัวเลือกที่ซ่อนไว้ทั้งหมดเนื่องจาก flatmaps ของสำหรับไวยากรณ์:

val confirm = for {
  venue <- lookupVenue(name)
  user <- getLoggedInUser(session)
  confno <- reserveTable(venue, user)
} yield {
  mailTo(confno, user)
  confno
}

ส่วนผลผลิตจะได้รับการดำเนินการหากทั้งสามฟังก์ชั่นมีSome[X]; ใดจะโดยตรงจะกลับไปNoneconfirm


ดังนั้น:

Monads อนุญาตให้คำนวณการสั่งซื้อได้ใน Functioning Programing ซึ่งทำให้เราสามารถจัดลำดับการเรียงลำดับของการกระทำในรูปแบบที่มีโครงสร้างที่ดีเหมือนกับ DSL

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

การเรียงลำดับและการทำเกลียวของการกระทำโดย Monad นั้นกระทำโดยผู้แปลภาษาที่ทำการเปลี่ยนแปลงผ่านเวทมนตร์แห่งการปิด


อย่างไรก็ตาม Monad ไม่เพียง แต่เป็นรูปแบบการคำนวณที่ใช้ใน FP เท่านั้น:

ทฤษฎีหมวดหมู่เสนอการคำนวณหลายรูปแบบ ในหมู่พวกเขา

  • รูปแบบการคำนวณลูกศร
  • แบบจำลองการคำนวณของ Monad
  • รูปแบบการประยุกต์ใช้การคำนวณ

2
ฉันชอบคำอธิบายนี้! ตัวอย่างที่คุณแสดงให้เห็นถึงแนวคิดที่สวยงามและยังเพิ่มสิ่งที่ IMHO หายไปจากทีเซอร์ของ Eric เกี่ยวกับ SelectMany () เป็น Monad ขอบคุณสำหรับสิ่งนี้!
aoven

1
IMHO นี่คือคำตอบที่หรูหราที่สุด
Polymerase

และก่อนที่ทุกอย่างอื่น Functor
Will Ness

34

ในการเคารพผู้อ่านที่รวดเร็วฉันเริ่มต้นด้วยคำจำกัดความที่แม่นยำก่อนดำเนินการต่อด้วยคำอธิบาย "ภาษาอังกฤษธรรมดา" ที่รวดเร็วยิ่งขึ้นแล้วเลื่อนไปที่ตัวอย่าง

นี่คือทั้งกระชับและนิยาม reworded เล็กน้อย

monad (วิทยาการคอมพิวเตอร์) อย่างเป็นทางการของแผนที่ที่:

  • ส่งทุกชนิดXของภาษาการเขียนโปรแกรมที่กำหนดไปยังชนิดใหม่T(X)(เรียกว่า "ชนิดของT-computations ด้วยค่าในX");

  • พร้อมกับกฎสำหรับการเขียนสองฟังก์ชั่นของแบบฟอร์ม f:X->T(Y)และg:Y->T(Z)ฟังก์ชั่นg∘f:X->T(Z);

  • ในลักษณะที่เชื่อมโยงกันในความรู้สึกที่ชัดเจนและเป็นเอกภาพด้วยความเคารพต่อหน่วยงานที่ได้รับเรียกว่าpure_X:X->T(X)ให้คิดว่าเป็นคุณค่าของการคำนวณที่บริสุทธิ์เพียงแค่คืนค่านั้น

ดังนั้นในคำง่าย ๆmonadคือกฎที่ส่งผ่านจากประเภทหนึ่งXไปยังอีกประเภทหนึ่งT(X)และกฎที่จะส่งผ่านจากสองฟังก์ชันf:X->T(Y)และg:Y->T(Z)(ที่คุณต้องการเขียน แต่ไม่สามารถ) ไปยังฟังก์ชันใหม่h:X->T(Z)ได้ ซึ่งอย่างไรก็ตามไม่ใช่องค์ประกอบในแง่คณิตศาสตร์อย่างเข้มงวด โดยพื้นฐานแล้วเรามีองค์ประกอบ "ดัด" ของฟังก์ชั่นหรือกำหนดวิธีการประกอบฟังก์ชั่นใหม่

นอกจากนี้เราต้องการกฎของการเขียน monad เพื่อตอบสนองความจริงทางคณิตศาสตร์ "ชัดเจน":

  • การเชื่อมโยง : การเขียนfด้วยgและจากนั้นกับh(จากภายนอก) ควรจะเหมือนกับการเขียนgด้วยhและจากนั้นกับf(จากภายใน)
  • คุณสมบัติ Unital : การเขียนfกับตัวตนของfฟังก์ชั่นทั้งสองข้างควรผลผลิต

อีกครั้งในคำง่ายๆเราไม่สามารถบ้านิยามองค์ประกอบของฟังก์ชั่นของเราอีกครั้งได้ตามที่เราต้องการ:

  • ก่อนอื่นเราต้องมีการเชื่อมโยงเพื่อให้สามารถเขียนฟังก์ชั่นได้หลายอย่างในแถวf(g(h(k(x)))เดียวและไม่ต้องกังวลกับการระบุฟังก์ชั่นการเรียงลำดับคู่ เมื่อกฎ monad กำหนดวิธีการเขียนฟังก์ชั่นคู่หนึ่งโดยปราศจากความจริงเราจะต้องรู้ว่าคู่ใดประกอบกันก่อน (โปรดทราบว่าแตกต่างจากคุณสมบัติการสับเปลี่ยนที่fประกอบด้วยgเหมือนกันgกับfซึ่งไม่จำเป็นต้องใช้)
  • และประการที่สองเราต้องการคุณสมบัติที่เป็นเอกภาพซึ่งก็คือการพูดว่าอัตลักษณ์นั้นประกอบไปด้วยวิธีที่เราคาดหวังไว้เล็กน้อย ดังนั้นเราสามารถ refactor ได้อย่างปลอดภัยฟังก์ชั่นเมื่อใดก็ตามที่สามารถดึงตัวตนเหล่านั้น

ดังนั้นอีกครั้งโดยย่อ: Monad เป็นกฎของการขยายประเภทและฟังก์ชั่นการเขียนที่ตอบสนองความจริงสองประการคือความสัมพันธ์และคุณสมบัติที่เป็นเอกภาพ

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

ที่เป็นหลักมันสั้น


เป็นนักคณิตศาสตร์มืออาชีพฉันชอบที่จะหลีกเลี่ยงการเรียกhว่า "องค์ประกอบ" ของและf gเพราะในทางคณิตศาสตร์มันไม่ใช่ เรียกว่า "การจัดองค์ประกอบ" อย่างไม่ถูกต้องสันนิษฐานว่าhเป็นองค์ประกอบทางคณิตศาสตร์ที่แท้จริงซึ่งมันไม่ใช่ มันไม่ได้กำหนดไว้โดยไม่ซ้ำกันและf gแต่เป็นผลจากฟังก์ชั่น "กฎการเขียน" ใหม่ของเรา ซึ่งสามารถแตกต่างอย่างสิ้นเชิงจากองค์ประกอบทางคณิตศาสตร์ที่เกิดขึ้นจริงแม้ว่าจะมีอยู่หลัง!


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

ข้อยกเว้นการขว้างเป็นตัวอย่างของ Monad

สมมติว่าเราต้องการเขียนสองฟังก์ชัน:

f: x -> 1 / x
g: y -> 2 * y

แต่f(0)ไม่ได้กำหนดไว้ดังนั้นจึงมีข้อeผิดพลาดเกิดขึ้น จากนั้นคุณจะกำหนดค่าองค์ประกอบได้g(f(0))อย่างไร โยนข้อยกเว้นอีกครั้งแน่นอน! eบางทีเดียวกัน e1อาจจะยกเว้นการปรับปรุงใหม่

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

อะไรคือข้อยกเว้น?

นี่เป็นเรื่องเล็กน้อยสำหรับโปรแกรมเมอร์ที่มีประสบการณ์ แต่ฉันอยากจะพูดอะไรซักอย่างเพื่อกำจัดหนอนแห่งความสับสน:

ข้อยกเว้นเป็นวัตถุที่ห่อหุ้มข้อมูลเกี่ยวกับวิธีการที่ผลลัพธ์ที่ไม่ถูกต้องของการดำเนินการเกิดขึ้น

สิ่งนี้สามารถอยู่ในช่วงจากการทิ้งรายละเอียดใด ๆ และส่งกลับค่าส่วนกลางเดียว (เช่นNaNหรือnull) หรือสร้างรายการบันทึกที่ยาวนานหรือสิ่งที่เกิดขึ้นส่งไปยังฐานข้อมูลและทำซ้ำทั่วชั้นจัดเก็บข้อมูลแบบกระจาย;)

แตกต่างที่สำคัญระหว่างทั้งสองตัวอย่างรุนแรงของข้อยกเว้นคือว่าในกรณีแรกที่มีไม่มีผลข้างเคียง ในครั้งที่สองมี ซึ่งนำเราไปสู่คำถาม (พันดอลลาร์):

มีข้อยกเว้นในฟังก์ชั่นแท้หรือไม่

คำตอบที่สั้นกว่า : ใช่ แต่เฉพาะเมื่อไม่มีผลข้างเคียง

คำตอบอีกต่อไป เพื่อให้บริสุทธิ์เอาต์พุตของฟังก์ชั่นของคุณจะต้องได้รับการพิจารณาโดยไม่ซ้ำกันโดยอินพุต ดังนั้นเราจึงแก้ไขฟังก์ชันของเราfโดยส่ง0ค่านามธรรมใหม่eที่เราเรียกว่าข้อยกเว้น เราตรวจสอบให้แน่ใจว่าค่ามีข้อมูลภายนอกที่ไม่มีไม่ได้กำหนดที่ไม่ซ้ำกันโดยการป้อนข้อมูลของเราซึ่งเป็นe xดังนั้นนี่คือตัวอย่างของข้อยกเว้นที่ไม่มีผลข้างเคียง:

e = {
  type: error, 
  message: 'I got error trying to divide 1 by 0'
}

และนี่คือสิ่งเดียวที่มีผลข้างเคียง:

e = {
  type: error, 
  message: 'Our committee to decide what is 1/0 is currently away'
}

ที่จริงแล้วมันมีผลข้างเคียงหากข้อความนั้นสามารถเปลี่ยนแปลงได้ในอนาคต แต่ถ้ามันรับประกันว่าจะไม่เปลี่ยนแปลงค่านั้นจะสามารถคาดการณ์ได้โดยไม่ซ้ำกันดังนั้นจึงไม่มีผลข้างเคียง

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

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

ประเภทส่วนขยาย

ในขณะที่เราต้องการเปลี่ยนแปลงข้อความภายในข้อยกเว้นของเราเราจะประกาศประเภทใหม่Eสำหรับวัตถุข้อยกเว้นทั้งหมดแล้วนั่นคือสิ่งที่maybe numberนอกเหนือจากชื่อที่สับสนซึ่งจะเป็นประเภทnumberหรือประเภทข้อยกเว้นใหม่Eดังนั้นมันเป็นจริงสหภาพnumber | Eของและnumber Eโดยเฉพาะอย่างยิ่งมันขึ้นอยู่กับวิธีการที่เราต้องการที่จะสร้างซึ่งไม่มีปัญหามิได้สะท้อนให้เห็นในชื่อEmaybe number

องค์ประกอบการทำงานคืออะไร?

มันเป็นเรื่องที่ดำเนินการทางคณิตศาสตร์การฟังก์ชั่น f: X -> Yและg: Y -> Zและการสร้างองค์ประกอบของพวกเขาเป็นฟังก์ชั่นที่น่าพอใจh: X -> Z h(x) = g(f(x))ปัญหาเกี่ยวกับคำนิยามนี้เกิดขึ้นเมื่อผลที่ไม่ได้รับอนุญาตเป็นข้อโต้แย้งของf(x)g

ในวิชาคณิตศาสตร์ฟังก์ชั่นเหล่านั้นไม่สามารถประกอบได้หากไม่มีงานพิเศษ การแก้ปัญหาทางคณิตศาสตร์อย่างเคร่งครัดสำหรับตัวอย่างข้างต้นของเราfและgคือการลบจากชุดของคำจำกัดความของ0 fกับที่ชุดใหม่ของคำนิยาม (แบบใหม่ที่เข้มงวดมากขึ้นของx) fกลายเป็น composable gกับ

อย่างไรก็ตามมันไม่ได้เป็นประโยชน์ในการเขียนโปรแกรมเพื่อ จำกัด ชุดของคำจำกัดความfเช่นนั้น สามารถใช้ข้อยกเว้นแทนได้

หรือเป็นอีกวิธีหนึ่งคุณค่าเทียมที่ถูกสร้างขึ้นเช่นNaN, undefined, null, Infinityฯลฯ ดังนั้นคุณประเมิน1/0การInfinityและการ1/-0 -Infinityจากนั้นบังคับค่าใหม่ให้กลับไปสู่นิพจน์ของคุณแทนการโยนข้อยกเว้น นำไปสู่ผลลัพธ์ที่คุณอาจจะหรืออาจจะไม่สามารถคาดเดาได้:

1/0                // => Infinity
parseInt(Infinity) // => NaN
NaN < 0            // => false
false + 1          // => 1

และเรากลับไปที่หมายเลขปกติพร้อมที่จะไป;)

JavaScript ช่วยให้เราสามารถดำเนินการนิพจน์ที่เป็นตัวเลขได้ในค่าใช้จ่ายใด ๆ โดยไม่ทิ้งข้อผิดพลาดดังตัวอย่างด้านบน นั่นหมายความว่ามันยังช่วยให้การเขียนฟังก์ชั่น ซึ่งเป็นสิ่งที่เกี่ยวกับ monad - มันเป็นกฎในการเขียนฟังก์ชั่นที่ตอบสนองความจริงตามที่กำหนดไว้ในตอนต้นของคำตอบนี้

แต่กฎของฟังก์ชั่นการเขียนเกิดขึ้นจากการใช้งาน JavaScript เพื่อจัดการกับข้อผิดพลาดที่เป็นตัวเลขหรือไม่?

ในการตอบคำถามนี้สิ่งที่คุณต้องทำคือตรวจสอบความจริง (จากการออกกำลังกายไม่ใช่ส่วนหนึ่งของคำถามที่นี่;)

สามารถใช้ข้อยกเว้นในการขว้างปาเพื่อสร้าง Monad ได้หรือไม่?

แท้จริง monad มีประโยชน์มากขึ้นแทนจะกำหนดกฎว่าถ้าfโยนข้อยกเว้นสำหรับบางคนจึงไม่องค์ประกอบใด ๆx gบวกทำให้ข้อยกเว้นEทั่วโลกมีความเป็นไปได้เพียงค่าเดียวเท่านั้น ( วัตถุเทอร์มินัลในทฤษฎีหมวดหมู่) ตอนนี้สัจพจน์ทั้งสองสามารถตรวจสอบได้ทันทีและเราได้รับ monad ที่มีประโยชน์มาก และผลที่ได้คือสิ่งที่เป็นที่รู้จักกันดีในฐานะอาจ monad


3
ผลงานที่ดี +1 แต่บางทีคุณอาจต้องการลบ "พบคำอธิบายส่วนใหญ่ยาวเกินไป ... " เป็นของคุณที่ยาวที่สุด คนอื่น ๆ จะตัดสินว่ามันเป็น "ภาษาอังกฤษธรรมดา" ตามที่ต้องการคำถาม: "ภาษาอังกฤษธรรมดา == ในคำง่าย ๆ ในวิธีที่ง่าย"
cibercitizen1

@ cibercitizen1 ขอบคุณ! จริง ๆ แล้วมันสั้นถ้าคุณไม่นับตัวอย่าง จุดหลักคือการที่คุณไม่จำเป็นต้องอ่านตัวอย่างเพื่อให้เข้าใจความหมาย น่าเสียดายที่คำอธิบายมากมายบังคับให้ฉันอ่านตัวอย่างก่อนซึ่งบ่อยครั้งไม่จำเป็น แต่แน่นอนว่าอาจต้องใช้งานเพิ่มเติมสำหรับนักเขียน ด้วยการพึ่งพาตัวอย่างที่เฉพาะเจาะจงมากเกินไปมีอันตรายที่รายละเอียดที่ไม่สำคัญบดบังภาพและทำให้ยากต่อการเข้าใจ ต้องบอกว่าคุณมีคะแนนที่ถูกต้องดูการปรับปรุง
Dmitri Zaitsev

2
ยาวเกินไปและสับสน
seeimurugan

1
@seenimurugan ยินดีรับคำแนะนำในการปรับปรุง;)
Dmitri Zaitsev

26

Monad เป็นชนิดข้อมูลที่ห่อหุ้มคุณค่าและการดำเนินการสองอย่างสามารถนำไปใช้:

  • return x สร้างค่าของประเภท monad ที่ encapsulate x
  • m >>= f(อ่านมันในฐานะ "ตัวดำเนินการเชื่อมโยง") ใช้ฟังก์ชันfกับค่าใน monadm

นั่นคือสิ่งที่ monad คือ มีเทคนิคอีกสองสามอย่างแต่โดยพื้นฐานแล้วการปฏิบัติการสองอย่างนั้นกำหนด monad คำถามที่แท้จริงคือ "monad ทำอะไรได้บ้าง" และขึ้นอยู่กับ monad - รายการคือ monads, Maybes เป็น monads, ปฏิบัติการ IO เป็น monads ทั้งหมดว่ามันหมายความว่าเมื่อเราบอกว่าสิ่งเหล่านี้จะ monads ที่พวกเขามีอินเตอร์เฟซของ monad และreturn>>=


“ สิ่งที่ monad ทำและขึ้นอยู่กับ monad”: และที่แม่นยำยิ่งขึ้นนั้นขึ้นอยู่กับbindฟังก์ชั่นที่ต้องกำหนดสำหรับ monadic แต่ละประเภทใช่มั้ย นั่นเป็นเหตุผลที่ดีที่จะไม่สร้างความสับสนให้กับการเรียงเนื่องจากมีคำจำกัดความเดียวสำหรับการแต่งเพลงในขณะที่ไม่มีคำจำกัดความเดียวสำหรับฟังก์ชั่นการผูกมีหนึ่งประเภทต่อ monadic ถ้าฉันเข้าใจอย่างถูกต้อง
Hibou57

14

จากวิกิพีเดีย :

ในการเขียนโปรแกรมการทำงาน monad เป็นชนิดข้อมูลนามธรรมที่ใช้เพื่อแสดงการคำนวณ (แทนข้อมูลในโมเดลโดเมน) Monads อนุญาตให้โปรแกรมเมอร์เชื่อมโยงแอ็คชันเข้าด้วยกันเพื่อสร้างไพพ์ไลน์ซึ่งแต่ละแอ็คชันถูกตกแต่งด้วยกฎการประมวลผลเพิ่มเติมที่จัดทำโดย monad โปรแกรมที่เขียนในรูปแบบการใช้งานสามารถใช้ monads เพื่อโพรซีเดอร์โครงสร้างที่มีการดำเนินการตามลำดับ, 1 [2] หรือเพื่อกำหนดโฟลว์การควบคุมตามอำเภอใจ (เช่นการจัดการภาวะพร้อมกันการต่อเนื่องหรือข้อยกเว้น)

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

โปรแกรมเมอร์จะเขียนฟังก์ชั่น monadic เพื่อกำหนดขั้นตอนการประมวลผลข้อมูล ทำหน้าที่เหมือนกรอบ monad มันเป็นพฤติกรรมที่นำมาใช้ใหม่เพื่อตัดสินใจว่าจะทำหน้าที่เฉพาะในท่อ monadic เรียกและจัดการสายลับงานที่ต้องคำนวณ [3] ผู้ประกอบการที่ผูกและส่งคืน interleaved ในไปป์ไลน์จะถูกดำเนินการหลังจากแต่ละฟังก์ชัน monadic ส่งคืนการควบคุมและจะดูแลด้านเฉพาะที่จัดการโดย monad

ฉันเชื่อว่ามันอธิบายได้ดีมาก


12

ฉันจะพยายามทำให้คำจำกัดความที่สั้นที่สุดที่ฉันสามารถจัดการได้โดยใช้คำศัพท์ OOP:

คลาสทั่วไปCMonadic<T>เป็น monad ถ้ากำหนดอย่างน้อยวิธีต่อไปนี้:

class CMonadic<T> { 
    static CMonadic<T> create(T t);  // a.k.a., "return" in Haskell
    public CMonadic<U> flatMap<U>(Func<T, CMonadic<U>> f); // a.k.a. "bind" in Haskell
}

และหากกฎหมายต่อไปนี้ใช้กับ T ทุกประเภทและค่าที่เป็นไปได้ t

ตัวตนด้านซ้าย:

CMonadic<T>.create(t).flatMap(f) == f(t)

ตัวตนที่ถูกต้อง

instance.flatMap(CMonadic<T>.create) == instance

associativity:

instance.flatMap(f).flatMap(g) == instance.flatMap(t => f(t).flatMap(g))

ตัวอย่าง :

รายชื่อ monad อาจมี:

List<int>.create(1) --> [1]

และ flatMap ในรายการ [1,2,3] สามารถทำงานได้เช่น:

intList.flatMap(x => List<int>.makeFromTwoItems(x, x*10)) --> [1,10,2,20,3,30]

Iterables และ Observables สามารถสร้างเป็น monadic รวมถึง Promises and Tasks

ความเห็น :

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

intList.flatMap(x => List<int>.makeFromTwo(x, x*10))
       .flatMap(x => x % 3 == 0 
                   ? List<string>.create("x = " + x.toString()) 
                   : List<string>.empty())

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

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

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

interface IMonad<T> { 
    static IMonad<T> create(T t); // not allowed
    public IMonad<U> flatMap<U>(Func<T, IMonad<U>> f); // not specific enough,
    // because the function must return the same kind of monad, not just any monad
}

7

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

Monad เป็น monoid ในหมวดหมู่ของ endofunctors

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

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

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

หนึ่งสามารถใช้หม้อแปลง monad เพื่อจุดประสงค์นี้และมีคอลเลกชันคุณภาพสูงของ monads "มาตรฐาน" ทั้งหมด:

  • รายการ (การคำนวณที่ไม่ได้กำหนดไว้ล่วงหน้าโดยดำเนินการกับรายการเป็นโดเมน)
  • อาจเป็น (การคำนวณที่สามารถล้มเหลวได้ แต่การรายงานใดไม่สำคัญ)
  • ข้อผิดพลาด (การคำนวณที่สามารถล้มเหลวและต้องการการจัดการข้อยกเว้น
  • ผู้อ่าน (การคำนวณที่สามารถแสดงได้ด้วยการจัดองค์ประกอบของฟังก์ชัน Haskell ธรรมดา)
  • นักเขียน (การคำนวณด้วย "เรนเดอร์" / "การบันทึก" ตามลำดับ (ไปยังสตริง, html ฯลฯ )
  • ต่อเนื่อง (ต่อเนื่อง)
  • IO (การคำนวณที่ขึ้นอยู่กับระบบคอมพิวเตอร์พื้นฐาน)
  • รัฐ (การคำนวณที่บริบทมีค่าที่แก้ไขได้)

กับ monad transformers และ class types ที่เกี่ยวข้อง คลาสประเภทอนุญาตให้มีวิธีการเสริมการรวม monads โดยรวมอินเทอร์เฟซของพวกเขาเพื่อให้ monads คอนกรีตสามารถใช้อินเทอร์เฟซมาตรฐานสำหรับ monad "ชนิด" ตัวอย่างเช่นโมดูล Control.Monad.State มีคลาส MonadState sm และ (สถานะ) เป็นตัวอย่างของแบบฟอร์ม

instance MonadState s (State s) where
    put = ...
    get = ...

เรื่องราวที่ยาวนานคือ monad เป็น functor ซึ่งแนบ "บริบท" กับค่าซึ่งมีวิธีการแทรกค่าลงใน monad และมีวิธีการประเมินค่าที่เกี่ยวกับบริบทที่แนบกับมันอย่างน้อย ในทางที่ จำกัด

ดังนั้น:

return :: a -> m a

เป็นฟังก์ชั่นที่แทรกค่าของประเภท a ลงใน monad "action" ของ type m a

(>>=) :: m a -> (a -> m b) -> m b

เป็นฟังก์ชันที่ใช้การกระทำ monad ประเมินผลลัพธ์และใช้ฟังก์ชันกับผลลัพธ์ สิ่งที่เรียบร้อยเกี่ยวกับ (>> =) คือผลลัพธ์นั้นอยู่ใน monad เดียวกัน กล่าวอีกนัยหนึ่งใน m >> = f, (>> =) ดึงผลลัพธ์ออกจาก m และผูกกับ f เพื่อให้ผลลัพธ์อยู่ใน monad (อีกวิธีหนึ่งเราสามารถพูดได้ว่า (>> =) ดึง f เป็น m และนำไปใช้กับผลลัพธ์) หากเรามี f :: a -> mb และ g :: b -> mc เราสามารถ การกระทำ "ลำดับ":

m >>= f >>= g

หรือใช้ "do notation"

do x <- m
   y <- f x
   g y

ประเภทของ (>>) อาจส่องสว่าง มันคือ

(>>) :: m a -> m b -> m b

มันสอดคล้องกับผู้ประกอบการ (;) ในภาษาขั้นตอนเช่น C. จะช่วยให้โน้ตที่ชอบ:

m = do x <- someQuery
       someAction x
       theNextAction
       andSoOn

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

join :: m (m a) -> m a

ที่สำคัญคือหมายความว่า monad ถูกปิดภายใต้การดำเนินการ "เลเยอร์ซ้อน" นี่คือการทำงานของหม้อแปลง monad: พวกเขารวม monads โดยให้วิธีการ "เข้าร่วมเหมือน" สำหรับประเภทเช่น

newtype MaybeT m a = MaybeT { runMaybeT :: m (Maybe a) }

เพื่อให้เราสามารถเปลี่ยนการกระทำใน (อาจจะ m) เป็นการกระทำใน m, ยุบชั้นได้อย่างมีประสิทธิภาพ ในกรณีนี้ runMaybeT :: MaybeT ma -> m (อาจเป็น) เป็นวิธีเข้าร่วมของเรา (อาจจะเป็น m) เป็น monad และอาจจะ :: (อาจเป็น) -> อาจจะเป็นตัวสร้างได้อย่างมีประสิทธิภาพสำหรับการกระทำ monad แบบใหม่ใน m

ฟรี monad สำหรับ functor คือ monad ที่สร้างขึ้นโดยการซ้อน f โดยมีความหมายว่าทุกลำดับของ constructors สำหรับ f เป็นองค์ประกอบของ monad ที่ว่าง (หรือมากกว่านั้นบางสิ่งที่มีรูปร่างเหมือนต้นไม้ลำดับของ constructors สำหรับ ฉ) Monads ฟรีเป็นเทคนิคที่มีประโยชน์สำหรับการสร้าง monads ที่ยืดหยุ่นด้วยจำนวนแผ่นบอยเลอร์น้อยที่สุด ในโปรแกรม Haskell ฉันอาจใช้ monads ฟรีเพื่อกำหนด monads ง่าย ๆ สำหรับ "การเขียนโปรแกรมระบบระดับสูง" เพื่อช่วยรักษาความปลอดภัยของประเภท (ฉันแค่ใช้ประเภทและประกาศของพวกเขาการใช้งานเป็นแบบตรงไปตรงมากับการใช้ combinators):

data RandomF r a = GetRandom (r -> a) deriving Functor
type Random r a = Free (RandomF r) a


type RandomT m a = Random (m a) (m a) -- model randomness in a monad by computing random monad elements.
getRandom     :: Random r r
runRandomIO   :: Random r a -> IO a (use some kind of IO-based backend to run)
runRandomIO'  :: Random r a -> IO a (use some other kind of IO-based backend)
runRandomList :: Random r a -> [a]  (some kind of list-based backend (for pseudo-randoms))

Monadism เป็นสถาปัตยกรรมพื้นฐานสำหรับสิ่งที่คุณอาจเรียกรูปแบบ "interpreter" หรือ "command" ซึ่งแยกออกเป็นรูปแบบที่ชัดเจนเนื่องจากการคำนวณแบบ monadic ทุกตัวต้องเป็น "run" อย่างน้อยที่สุด (ระบบรันไทม์เรียกใช้ IO monad สำหรับเราและเป็นจุดเริ่มต้นของโปรแกรม Haskell ใด ๆ IO "ไดรฟ์" ส่วนที่เหลือของการคำนวณโดยเรียกใช้การกระทำ IO ตามลำดับ)

ประเภทของการเข้าร่วมเป็นที่ที่เราได้รับข้อความว่า monad เป็น monoid ในหมวดหมู่ของ endofunctors โดยทั่วไปแล้วการเข้าร่วมจะมีความสำคัญมากกว่าสำหรับวัตถุประสงค์ทางทฤษฎีโดยอาศัยประเภทของมัน แต่ความเข้าใจประเภทหมายถึงการเข้าใจพระ ประเภทการเข้าร่วมของหม้อแปลงรวมและ monad เป็นองค์ประกอบที่มีประสิทธิภาพของ endofunctors ในแง่ขององค์ประกอบการทำงาน ในการใช้ภาษาหลอกแบบ Haskell

Foo :: m (ma) <-> (m. m)


3

Monad คืออาร์เรย์ของฟังก์ชั่น

(Pst: อาร์เรย์ของฟังก์ชันเป็นเพียงการคำนวณ)

ที่จริงแล้วแทนที่จะเป็นอาร์เรย์ที่แท้จริง (หนึ่งฟังก์ชั่นในหนึ่งเซลล์อาร์เรย์) คุณมีฟังก์ชั่นเหล่านั้นถูกล่ามโซ่โดยฟังก์ชั่นอื่น >> = >> = ช่วยให้สามารถปรับผลลัพธ์จากฟังก์ชั่น i เป็นฟีดฟังก์ชั่น i + 1 ทำการคำนวณระหว่างพวกเขาหรือแม้กระทั่งไม่เรียกใช้ฟังก์ชัน i + 1

ประเภทที่ใช้ที่นี่คือ "ประเภทที่มีบริบท" นี่คือค่าที่มี "แท็ก" ฟังก์ชั่นที่ถูกล่ามโซ่จะต้องใช้ "ค่าเปล่า" และส่งกลับผลการติดแท็ก หนึ่งในหน้าที่ของ >> = คือการแยกค่าที่ไม่ได้ใช้ออกจากบริบท นอกจากนี้ยังมีฟังก์ชั่น "คืนค่า" ซึ่งรับค่าเปล่าและใส่ไว้กับแท็ก

ตัวอย่างที่มีบางที ลองใช้มันเพื่อเก็บจำนวนเต็มอย่างง่ายที่ใช้ทำการคำนวณ

-- a * b
multiply :: Int -> Int -> Maybe Int
multiply a b = return  (a*b)

-- divideBy 5 100 = 100 / 5
divideBy :: Int -> Int -> Maybe Int
divideBy 0 _ = Nothing -- dividing by 0 gives NOTHING
divideBy denom num = return (quot num denom) -- quotient of num / denom

-- tagged value
val1 = Just 160 

-- array of functions feeded with val1
array1 = val1 >>= divideBy 2  >>= multiply 3 >>= divideBy  4 >>= multiply 3

-- array of funcionts created with the do notation
-- equals array1 but for the feeded val1
array2 :: Int -> Maybe Int
array2 n = do
       v <- divideBy 2  n
       v <- multiply 3 v
       v <- divideBy 4 v
       v <- multiply 3 v
       return v

-- array of functions, 
-- the first >>= performs 160 / 0, returning Nothing
-- the second >>= has to perform Nothing >>= multiply 3 ....
-- and simply returns Nothing without calling multiply 3 ....
array3 = val1 >>= divideBy 0  >>= multiply 3 >>= divideBy  4 >>= multiply 3

main = do
     print array1
     print (array2 160)
     print array3

เพียงเพื่อแสดงว่า monads เป็นอาร์เรย์ของฟังก์ชันที่มีการดำเนินการตัวช่วยพิจารณาตัวอย่างที่เทียบเท่ากับข้างต้นเพียงแค่ใช้ฟังก์ชันอาร์เรย์จริง

type MyMonad = [Int -> Maybe Int] -- my monad as a real array of functions

myArray1 = [divideBy 2, multiply 3, divideBy 4, multiply 3]

-- function for the machinery of executing each function i with the result provided by function i-1
runMyMonad :: Maybe Int -> MyMonad -> Maybe Int
runMyMonad val [] = val
runMyMonad Nothing _ = Nothing
runMyMonad (Just val) (f:fs) = runMyMonad (f val) fs

และมันจะถูกใช้เช่นนี้:

print (runMyMonad (Just 160) myArray1)

1
Super-เรียบร้อย! ดังนั้นการผูกเป็นเพียงวิธีการในการประเมินอาร์เรย์ของฟังก์ชั่นที่มีบริบทในลำดับการป้อนข้อมูลด้วยบริบท :) ต์
มูซาอัล hassy

>>=เป็นผู้ให้บริการ
2418306

1
ฉันคิดว่าการเปรียบเทียบ "ฟังก์ชั่นต่างๆ" ไม่ชัดเจนมากนัก ถ้า\x -> x >>= k >>= l >>= mเป็นฟังก์ชั่นมากมายเช่นนั้นก็คือh . g . fซึ่งไม่เกี่ยวข้องกับ monads เลย
duplode

เราอาจกล่าวได้ว่าfunctorsไม่ว่าจะเป็นเอก, applicative หรือธรรมดาที่เกี่ยวกับ"โปรแกรม embellished" 'applicative' เพิ่มการโยงและ 'monad' เพิ่มการพึ่งพา (เช่นการสร้างขั้นตอนการคำนวณถัดไปขึ้นอยู่กับผลลัพธ์จากขั้นตอนการคำนวณก่อนหน้า)
Will Ness

3

ในแง่ของ OO monad เป็นภาชนะบรรจุที่คล่องแคล่ว

ข้อกำหนดขั้นต่ำคือคำจำกัดความclass <A> Somethingที่สนับสนุนตัวสร้างSomething(A a)และอย่างน้อยหนึ่งวิธีSomething<B> flatMap(Function<A, Something<B>>)

อีกทั้งยังนับว่าคลาสของคุณมีวิธีการใดที่มีลายเซ็นSomething<B> work()ซึ่งรักษากฎของคลาส - คอมไพเลอร์ bakes ใน flatMap ในเวลารวบรวม

เหตุใด Monad จึงมีประโยชน์ เนื่องจากเป็นคอนเทนเนอร์ที่อนุญาตการดำเนินการที่สามารถใช้โซ่ได้ซึ่งเก็บรักษาซีแมนทิกส์ ยกตัวอย่างเช่นOptional<?>การดำรงไว้ซึ่งความหมายของ isPresent สำหรับOptional<String>, Optional<Integer>, Optional<MyClass>ฯลฯ

เป็นตัวอย่างคร่าวๆ

Something<Integer> i = new Something("a")
  .flatMap(doOneThing)
  .flatMap(doAnother)
  .flatMap(toInt)

หมายเหตุเราเริ่มต้นด้วยสตริงและลงท้ายด้วยจำนวนเต็ม สวยเท่ห์

ใน OO อาจใช้เวลาโบกมือเล็กน้อย แต่วิธีการใด ๆ กับ Something ที่ส่งคืนคลาสย่อยอื่นของ Something ตรงตามเกณฑ์ของฟังก์ชันคอนเทนเนอร์ที่ส่งคืนคอนเทนเนอร์ประเภทเดิม

นั่นคือวิธีที่คุณรักษาซีแมนทิกส์ - เช่นความหมายและการดำเนินการของคอนเทนเนอร์ไม่เปลี่ยนแปลงพวกเขาเพียงแค่ห่อและปรับปรุงวัตถุภายในคอนเทนเนอร์


2

Monads ในการใช้งานทั่วไปนั้นเทียบเท่ากับการทำงานของกลไกการจัดการข้อยกเว้นของโพรซีเดอร์

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

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

ในความเป็นจริงผลข้างเคียงไม่สามารถตัดออกได้ในโลกแห่งความเป็นจริงอันเนื่องมาจาก I / O Monads ในฟังก์ชั่นการเขียนโปรแกรมใช้เพื่อจัดการสิ่งนี้โดยการตั้งค่าการเรียกฟังก์ชั่นที่ถูกล่ามโซ่ (ซึ่งอาจทำให้เกิดผลลัพธ์ที่ไม่คาดคิด) และเปลี่ยนผลลัพธ์ที่ไม่คาดคิดให้เป็นข้อมูลที่ถูกห่อหุ้ม

การไหลของการควบคุมถูกเก็บรักษาไว้ แต่เหตุการณ์ที่ไม่คาดคิดถูกห่อหุ้มและจัดการอย่างปลอดภัย


2

คำอธิบาย Monads ง่ายด้วยกรณีศึกษาของมาร์เวลเป็นที่นี่

Monads เป็นรูปแบบนามธรรมที่ใช้ในการจัดลำดับฟังก์ชั่นที่ต้องพึ่งพาซึ่งมีผลบังคับใช้ มีผลบังคับใช้ที่นี่หมายความว่าพวกเขาส่งคืนประเภทในรูปแบบ F [A] เช่นตัวเลือก [A] โดยที่ตัวเลือกคือ F เรียกว่าตัวสร้างประเภท มาดูกันใน 2 ขั้นตอนง่ายๆ

  1. ด้านล่างองค์ประกอบของฟังก์ชั่นเป็นสกรรมกริยา ดังนั้นจาก A ถึง CI สามารถเขียน A => B และ B => C
 A => C   =   A => B  andThen  B => C

ป้อนคำอธิบายรูปภาพที่นี่

  1. อย่างไรก็ตามหากฟังก์ชั่นส่งคืนชนิดของเอฟเฟกต์เช่นตัวเลือก [A] เช่น A => F [B] การจัดองค์ประกอบไม่ทำงานไปที่ B เราต้องการ A => B แต่เรามี A => F [B]
    ป้อนคำอธิบายรูปภาพที่นี่

    เราต้องการตัวดำเนินการพิเศษ "ผูก" ที่รู้วิธีหลอมฟังก์ชันเหล่านี้ที่ส่งคืน F [A]

 A => F[C]   =   A => F[B]  bind  B => F[C]

"ผูก"ฟังก์ชั่นที่กำหนดไว้สำหรับเฉพาะF

นอกจากนี้ยังมี"return"ประเภทA => F [A]สำหรับAใด ๆ ที่กำหนดไว้สำหรับF ที่เฉพาะเจาะจงเช่นกัน ในการเป็น Monad นั้นFต้องมีฟังก์ชั่นสองอย่างนี้กำหนดไว้

ดังนั้นเราจึงสามารถสร้างฟังก์ชั่น effectful A => F [b]จากฟังก์ชั่นใด ๆ บริสุทธิ์A => B ,

 A => F[B]   =   A => B  andThen  return

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

  • "สุ่ม" ( ช่วง => สุ่ม [Int] )
  • "print" ( String => IO [()] )
  • "ลอง ... จับ" ฯลฯ

2

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



1

ดูคำตอบของฉันที่ "Monad คืออะไร"

มันเริ่มต้นด้วยตัวอย่างที่สร้างแรงบันดาลใจทำงานผ่านตัวอย่างมาตัวอย่างของ monad และกำหนด "monad" อย่างเป็นทางการ

มันจะไม่มีความรู้เกี่ยวกับการเขียนโปรแกรมฟังก์ชั่นและจะใช้ pseudocode ด้วย function(argument) := expressionไวยากรณ์ที่มีการแสดงออกที่เป็นไปได้ง่ายที่สุด

โปรแกรม C ++ นี้เป็นการใช้งาน pseudocode monad (สำหรับการอ้างอิง: Mเป็นตัวสร้างประเภทfeedคือการดำเนินการ "ผูก" และwrapเป็นการดำเนินการ "ส่งคืน")

#include <iostream>
#include <string>

template <class A> class M
{
public:
    A val;
    std::string messages;
};

template <class A, class B>
M<B> feed(M<B> (*f)(A), M<A> x)
{
    M<B> m = f(x.val);
    m.messages = x.messages + m.messages;
    return m;
}

template <class A>
M<A> wrap(A x)
{
    M<A> m;
    m.val = x;
    m.messages = "";
    return m;
}

class T {};
class U {};
class V {};

M<U> g(V x)
{
    M<U> m;
    m.messages = "called g.\n";
    return m;
}

M<T> f(U x)
{
    M<T> m;
    m.messages = "called f.\n";
    return m;
}

int main()
{
    V x;
    M<T> m = feed(f, feed(g, wrap(x)));
    std::cout << m.messages;
}

0

จากมุมมองของภาคปฏิบัติ (สรุปสิ่งที่ได้กล่าวไว้ในคำตอบก่อนหน้านี้จำนวนมากและบทความที่เกี่ยวข้อง) ดูเหมือนว่าสำหรับฉันแล้วว่าหนึ่งใน "วัตถุประสงค์" พื้นฐาน (หรือประโยชน์) ของ monad คือการใช้ประโยชน์จากการพึ่งพาวิธีการเรียกซ้ำ ฟังก์ชั่น aka หรือองค์ประกอบ (เช่นเมื่อ f1 เรียก f2 เรียก f3, f3 จำเป็นต้องได้รับการประเมินก่อน f2 ก่อน f1) เพื่อแสดงองค์ประกอบเรียงลำดับในลักษณะที่เป็นธรรมชาติโดยเฉพาะอย่างยิ่งในบริบทของรูปแบบการประเมินผลแบบขี้เกียจ (นั่นคือ เช่น "f3 (); f2 (); f1 ();" ใน C - เคล็ดลับที่ชัดเจนโดยเฉพาะอย่างยิ่งถ้าคุณคิดว่ากรณีที่ f3, f2 และ f1 จริง ๆ แล้วกลับไม่มีอะไร [ผูกมัดของพวกเขาเป็น f1 (f2 (f3)) มีวัตถุประสงค์เพื่อสร้างลำดับ])

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

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

นี้เป็นเพียงแง่มุมหนึ่ง แต่เป็นเตือนที่นี่


0

คำอธิบายที่ง่ายที่สุดที่ฉันสามารถนึกได้ก็คือว่า monads เป็นวิธีในการเขียนฟังก์ชั่นด้วยผลลัพธ์ที่จัดทำขึ้น (องค์ประกอบ Kleisli หรือ aka) เป็น "embelished" ฟังก์ชั่นที่มีลายเซ็นa -> (b, smth)ที่aและbประเภท (คิดว่าInt, Bool) ที่อาจจะแตกต่างจากคนอื่น ๆ แต่ไม่จำเป็นต้อง - และsmthเป็น "บริบท" หรือ "embelishment"

ประเภทของฟังก์ชั่นนี้ยังสามารถเขียนa -> m bที่mเทียบเท่ากับ smth"embelishment" ดังนั้นสิ่งเหล่านี้คือฟังก์ชั่นที่คืนค่าในบริบท (คิดว่าฟังก์ชั่นที่บันทึกการกระทำของพวกเขาอยู่ที่ไหนsmthข้อความบันทึกหรือฟังก์ชั่นที่ดำเนินการอินพุต \ เอาต์พุตและผลลัพธ์ของพวกเขาขึ้นอยู่กับผลลัพธ์ของการกระทำ IO)

monad คืออินเทอร์เฟซ ("typeclass") ที่ทำให้ผู้ใช้บอกวิธีการเขียนฟังก์ชั่นดังกล่าว ผู้ดำเนินการจำเป็นต้องกำหนดฟังก์ชั่นการแต่งเพลง(a -> m b) -> (b -> m c) -> (a -> m c)สำหรับประเภทใด ๆmที่ต้องการใช้อินเทอร์เฟซ (นี่คือการประพันธ์ Kleisli)

ดังนั้นถ้าเราบอกว่าเรามี tuple type (Int, String)แสดงผลลัพธ์ของการคำนวณบนInts ที่บันทึกการกระทำของพวกเขาด้วย(_, String)การเป็น "embelishment" - บันทึกการกระทำ - และสองฟังก์ชันincrement :: Int -> (Int, String)และtwoTimes :: Int -> (Int, String)เราต้องการได้รับฟังก์ชั่นincrementThenDouble :: Int -> (Int, String)ซึ่งเป็นองค์ประกอบ ของทั้งสองฟังก์ชั่นที่คำนึงถึงบันทึกด้วย

ในตัวอย่างที่กำหนดการใช้งานแบบ monad ของทั้งสองฟังก์ชั่นจะใช้กับค่าจำนวนเต็ม 2 incrementThenDouble 2(ซึ่งเท่ากับtwoTimes (increment 2)) จะส่งกลับ(6, " Adding 1. Doubling 3.")สำหรับผลลัพธ์ตัวกลางincrement 2เท่ากับ(3, " Adding 1.")และtwoTimes 3เท่ากับ(6, " Doubling 3.")

จากฟังก์ชั่นการจัดองค์ประกอบ Kleisli นี้เราสามารถรับฟังก์ชั่น monadic ได้ตามปกติ

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