monads เป็นทางเลือกที่ทำงานได้ (อาจจะดีกว่า) กับลำดับชั้นการสืบทอดหรือไม่


20

ฉันจะใช้คำอธิบายภาษาที่ไม่เชื่อเรื่องพระเจ้าของ monads เช่นนี้ก่อนอธิบาย monoids:

หนังสือคือ ( ๆ ) ชุดของฟังก์ชั่นที่ใช้บางชนิดเป็นพารามิเตอร์และส่งกลับชนิดเดียวกัน

monadเป็น ( ๆ ) ชุดของฟังก์ชั่นที่ใช้เป็นกระดาษห่อประเภทเป็นพารามิเตอร์และส่งกลับชนิดกระดาษห่อเดียว

หมายเหตุเหล่านั้นคือคำอธิบายไม่ใช่คำจำกัดความ รู้สึกอิสระที่จะโจมตีคำอธิบายนั้น!

ดังนั้นในภาษา OO monad อนุญาตให้มีองค์ประกอบการดำเนินการเช่น:

Flier<Duck> m = new Flier<Duck>(duck).takeOff().flyAround().land()

โปรดทราบว่า monad กำหนดและควบคุมซีแมนทิกส์ของการดำเนินการเหล่านั้นมากกว่าคลาสที่มีอยู่

ตามเนื้อผ้าในภาษา OO เราจะใช้ลำดับชั้นและการสืบทอดเพื่อให้ความหมายเหล่านั้น ดังนั้นเราจึงต้องการมีBirdระดับด้วยวิธีการtakeOff(), flyAround()และland()และเป็ดจะได้รับมรดกเหล่านั้น

แต่แล้วเราก็มีปัญหากับนกที่บินไม่ได้เพราะpenguin.takeOff()ล้มเหลว เราต้องหันไปใช้ข้อยกเว้นในการขว้างและจัดการ

นอกจากนี้เมื่อเราบอกว่าเพนกวินเป็นเราทำงานเป็นปัญหากับมรดกหลายตัวอย่างเช่นถ้าเรายังมีลำดับชั้นของBirdSwimmer

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

ดังนั้นในกรณีนี้เราจะมีFlier<T>monad เหมือนตัวอย่างด้านบน:

Flier<Duck> m = new Flier<Duck>(duck).takeOff().flyAround().land()

... Flier<Penguin>และเราจะไม่ยกตัวอย่าง เราสามารถใช้การพิมพ์แบบคงที่เพื่อป้องกันสิ่งนั้นไม่ให้เกิดขึ้นด้วยอินเทอร์เฟซของตัวทำเครื่องหมาย หรือการตรวจสอบความสามารถรันไทม์เพื่อประกันตัว แต่จริงๆแล้วโปรแกรมเมอร์ไม่ควรวางเพนกวินลงใน Flier ในลักษณะเดียวกันพวกเขาไม่ควรหารด้วยศูนย์

นอกจากนี้ยังสามารถใช้งานได้ทั่วไป นักบินไม่จำเป็นต้องเป็นนก ตัวอย่างเช่นFlier<Pterodactyl>หรือFlier<Squirrel>โดยไม่เปลี่ยนความหมายของประเภทบุคคลเหล่านั้น

เมื่อเราจำแนกซีแมนทิกส์โดยฟังก์ชั่นที่สามารถคอมโพสิตบนคอนเทนเนอร์ - แทนที่จะใช้กับประเภทของลำดับชั้น - มันจะแก้ไขปัญหาเก่า ๆ กับคลาสที่ "ชนิดของสิ่งที่ต้องทำไม่เหมาะ" กับลำดับชั้นที่เฉพาะเจาะจง นอกจากนี้ยังได้อย่างง่ายดายและช่วยให้ความหมายได้อย่างชัดเจนหลายชั้นเหมือนเช่นเดียวกับFlier<Duck> Swimmer<Duck>ดูเหมือนว่าเรากำลังดิ้นรนกับความไม่ตรงกันของอิมพีแดนซ์โดยการจำแนกพฤติกรรมกับลำดับชั้นของชั้นเรียน พระจัดการอย่างหรูหรา

ดังนั้นคำถามของฉันก็คือในทำนองเดียวกับที่เราได้ให้การสนับสนุนการแต่งเรื่องการถ่ายทอดทางพันธุกรรม

(BTW ฉันไม่แน่ใจว่าควรจะอยู่ที่นี่หรือใน Comp Sci แต่นี่ดูเหมือนจะเป็นปัญหาการสร้างแบบจำลองในทางปฏิบัติ แต่อาจจะดีกว่าที่นั่น)


1
ไม่แน่ใจว่าฉันเข้าใจวิธีการทำงาน: กระรอกและเป็ดไม่บินในแบบเดียวกันดังนั้นต้องดำเนินการ "บินกรรม" ในชั้นเรียนเหล่านั้น ... และนักบินต้องใช้วิธีการทำกระรอกและเป็ด บิน ... อาจจะเป็นอินเทอร์เฟซ Flier ทั่วไป ... โอ๊ะรอสักครู่ ... ฉันพลาดอะไรไปหรือเปล่า
assylias

ส่วนต่อประสานนั้นแตกต่างจากการสืบทอดคลาสเนื่องจากอินเตอร์เฟสกำหนดความสามารถในขณะที่การสืบทอดหน้าที่กำหนดลักษณะการทำงานจริง แม้จะอยู่ใน "องค์ประกอบเหนือการสืบทอด" การกำหนดอินเตอร์เฟสยังคงเป็นกลไกสำคัญ (เช่นความหลากหลาย) อินเทอร์เฟซไม่ทำงานเป็นปัญหาการสืบทอดหลายที่เหมือนกัน นอกจากนี้นักบินแต่ละคนสามารถให้คุณสมบัติความสามารถของอินเทอร์เฟซและผ่านเช่น "getFlightSpeed ​​()" หรือ "getManuverability ()" สำหรับคอนเทนเนอร์ที่จะใช้
Rob

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

yup ด้วยการลดเลือนริ้วรอยของการเพิ่มฟังก์ชั่นประกอบที่เก็บรักษาความหมาย ประเภทคอนเทนเนอร์แบบ Parameterized มีมาเป็นเวลานาน แต่ด้วยตัวของมันเองพวกมันไม่ได้ตอบว่าเป็นคำตอบที่สมบูรณ์ นั่นเป็นเหตุผลที่ฉันสงสัยว่ารูปแบบ monad มีบทบาทพื้นฐานมากกว่าหรือไม่
Rob

6
ฉันไม่เข้าใจคำอธิบายเกี่ยวกับ monoids และ monads ของคุณ คุณสมบัติที่สำคัญของ monoids คือมันเกี่ยวข้องกับการดำเนินการแบบไบนารีที่เชื่อมโยง (คิดว่าการบวกจุดลอยตัว, การคูณจำนวนเต็มหรือการต่อข้อมูลสตริง) Monad เป็นสิ่งที่เป็นนามธรรมที่รองรับการจัดลำดับการคำนวณที่หลากหลาย
Rufflewind

คำตอบ:


15

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

เท่าที่ฉันเข้าใจพวกเขาพระไม่มีอะไรเกี่ยวข้องกับมรดก ฉันจะบอกว่าทั้งสองสิ่งนี้มีฉากตั้งฉากมากขึ้นหรือน้อยลง: พวกเขาตั้งใจจะจัดการกับปัญหาที่แตกต่างกัน:

  1. พวกเขาสามารถใช้งานร่วมกันในประสาทสัมผัสอย่างน้อยสอง:
    • ตรวจสอบTypeclassopediaซึ่งครอบคลุมประเภทคลาสของ Haskell มากมาย คุณจะสังเกตเห็นว่ามีความสัมพันธ์ที่เหมือนมรดกระหว่างพวกเขา ยกตัวอย่างเช่น Monad นั้นสืบเชื้อสายมาจากการประยุกต์ซึ่งมันเองนั้นสืบทอดมาจาก Functor
    • ชนิดข้อมูลที่เป็นอินสแตนซ์ของ Monads สามารถเข้าร่วมในลำดับชั้นของคลาส โปรดจำไว้ว่า Monad เป็นเหมือนอินเทอร์เฟซมากกว่าการใช้กับประเภทที่ระบุจะบอกคุณบางอย่างเกี่ยวกับประเภทข้อมูล แต่ไม่ใช่ทุกอย่าง
  2. การพยายามใช้สิ่งใดสิ่งหนึ่งจะทำได้ยากและน่าเกลียด

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


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

  1. monad คือชุดของฟังก์ชั่นที่ใช้ประเภทคอนเทนเนอร์เป็นพารามิเตอร์และส่งคืนชนิดคอนเทนเนอร์เดียวกัน

    ไม่สิ่งนี้อยู่Monadใน Haskell: ประเภทที่กำหนดพารามิเตอร์m aพร้อมการนำไปใช้return :: a -> m aและ(>>=) :: m a -> (a -> m b) -> m bปฏิบัติตามกฎหมายต่อไปนี้:

    return a >>= k  ==  k a
    m >>= return  ==  m
    m >>= (\x -> k x >>= h)  ==  (m >>= k) >>= h
    

    มีบางกรณีของ Monad ที่ไม่ใช่คอนเทนเนอร์ ( (->) b) และมีบางคอนเทนเนอร์ที่ไม่ใช่ (และไม่สามารถสร้างได้) อินสแตนซ์ของ Monad ( Setเนื่องจากข้อ จำกัด ประเภทคลาส) ดังนั้นสัญชาตญาณ "ตู้คอนเทนเนอร์" จึงน่าสงสาร ดูสิ่งนี้สำหรับตัวอย่างเพิ่มเติม

  2. ดังนั้นในภาษา OO monad อนุญาตให้มีองค์ประกอบการดำเนินการเช่น:

      Flier<Duck> m = new Flier<Duck>(duck).takeOff().flyAround().land()
    

    ไม่เลย. ตัวอย่างนั้นไม่ต้องการ Monad ทั้งหมดที่ต้องการคือฟังก์ชั่นที่มีประเภทอินพุตและเอาต์พุตที่ตรงกัน นี่เป็นอีกวิธีในการเขียนซึ่งเน้นว่าเป็นเพียงแอปพลิเคชันฟังก์ชัน:

    Flier<Duck> m = land(flyAround(takeOff(new Flier<Duck>(duck))));
    

    ฉันเชื่อว่านี่เป็นรูปแบบที่เรียกว่า "ส่วนต่อประสานที่คล่องแคล่ว" หรือ "วิธีผูกมัด" (แต่ฉันไม่แน่ใจ)

  3. โปรดทราบว่า monad กำหนดและควบคุมซีแมนทิกส์ของการดำเนินการเหล่านั้นมากกว่าคลาสที่มีอยู่

    ชนิดข้อมูลที่ monads สามารถ (และเกือบจะทำ!) มีการดำเนินการที่ไม่เกี่ยวข้องกับ monads นี่คือตัวอย่างของ Haskell ที่ประกอบด้วยสามหน้าที่[]ซึ่งไม่มีส่วนเกี่ยวข้องกับ monads: []"กำหนดและควบคุม semantics ของการดำเนินการ" และ "class class" ไม่ได้ แต่ไม่เพียงพอที่จะสร้าง monad:

    \predicate -> length . filter predicate . reverse
    
  4. คุณได้ตั้งข้อสังเกตอย่างถูกต้องว่ามีปัญหากับการใช้ลำดับชั้นของคลาสเพื่อทำสิ่งต่างๆ อย่างไรก็ตามตัวอย่างของคุณไม่แสดงหลักฐานใด ๆ ที่พระสงฆ์สามารถ:

    • ทำผลงานได้ดีในสิ่งที่สืบทอดมานั้นดี
    • ทำผลงานได้ดีในสิ่งที่มรดกไม่ดี

3
ขอขอบคุณ! มากมายสำหรับฉันที่จะประมวลผล ฉันไม่รู้สึกแย่ - ฉันซาบซึ้งในความเข้าใจอย่างลึกซึ้ง ฉันจะรู้สึกแย่ยิ่งกว่าการแบกความคิดที่ไม่ดี :) (ไปที่จุดรวมทั้งหมดของการแลกเปลี่ยน!)
Rob

1
@RobY คุณยินดีต้อนรับ! อย่างไรก็ตามถ้าคุณไม่เคยได้ยินมาก่อนฉันแนะนำLYAHเพราะเป็นแหล่งเรียนรู้ที่ดีสำหรับ monads (และ Haskell!) เพราะมันมีตัวอย่างมากมาย (และฉันรู้สึกว่าการทำตัวอย่างมากมายเป็นวิธีที่ดีที่สุดในการ แก้ไขปัญหา monads)

มีมากมายที่นี่ ฉันไม่ต้องการที่จะล้นความคิดเห็น แต่ความเห็นไม่กี่: # 2 land(flyAround(takeOff(new Flier<Duck>(duck))))ไม่ทำงาน (อย่างน้อยใน OO) เพราะการก่อสร้างนั้นต้องมีการห่อหุ้มเพื่อให้ได้รายละเอียดของ Flier โดยการผูกมัดการเรียนในชั้นเรียนรายละเอียดของ Flier ยังคงซ่อนอยู่และสามารถรักษาความหมายของมันได้ นั่นคล้ายกับเหตุผลที่ Haskell monad ผูกไว้(a, M b)และไม่ใช่(M a, M b)เพื่อให้ monad ไม่ต้องเปิดเผยสถานะของมันในฟังก์ชั่น "action"
Rob

# 1 โชคไม่ดีที่ฉันพยายามที่จะเบลอการขาดโมนาที่เข้มงวดใน Haskell เนื่องจากการทำแผนที่ทุกอย่างไปที่ Haskell มีปัญหาใหญ่: การจัดองค์ประกอบของฟังก์ชั่นรวมถึงการวางองค์ประกอบบนคอนสตรัคเตอร์ที่คุณไม่สามารถทำได้อย่างง่ายดาย ดังนั้นunitจะกลายเป็นตัวสร้าง (ส่วนใหญ่) ในประเภทที่มีอยู่และbindกลายเป็น (ส่วนใหญ่) การดำเนินการรวบรวมเวลาโดยนัย(เช่นการผูกต้น) ที่ผูกฟังก์ชั่น "การกระทำ" กับชั้นเรียน หากคุณมีฟังก์ชั่นชั้นหนึ่งหรือคลาสฟังก์ชั่น <, Monad <B>> bindวิธีการที่จะสามารถผูกมัดปลาย แต่ฉันจะใช้การละเมิดที่ต่อไป ;)
Rob

# 3 เห็นด้วยและนั่นคือความงามของมัน หากFlier<Thing>ควบคุมซีแมนทิกส์ของการบินมันสามารถเปิดเผยข้อมูลและการปฏิบัติการมากมายที่รักษาซีแมนทิกส์ของการบินในขณะที่ความหมายเฉพาะของ "monad" นั้นเป็นเพียงแค่การทำให้เชนและ encapsulated ข้อกังวลเหล่านั้นอาจไม่เกี่ยวข้องกับชั้นเรียนภายใน monad (และกับสิ่งที่ฉันใช้อยู่): เช่นResource<String>มีคุณสมบัติ httpStatus แต่ String ไม่
Rob

1

ดังนั้นคำถามของฉันก็คือในทำนองเดียวกับที่เราได้ให้การสนับสนุนการแต่งเรื่องการถ่ายทอดทางพันธุกรรม

ในภาษาที่ไม่ใช่ OO ใช่ ในภาษา OO แบบดั้งเดิมฉันจะปฏิเสธไม่ได้

ปัญหาคือภาษาส่วนใหญ่ไม่มีความชำนาญเฉพาะด้านประเภทซึ่งหมายความว่าคุณไม่สามารถสร้างFlier<Squirrel>และFlier<Bird>มีการใช้งานที่แตกต่างกัน คุณต้องทำสิ่งที่ชอบstatic Flier Flier::Create(Squirrel)(แล้วเกินพิกัดสำหรับแต่ละประเภท) ซึ่งหมายความว่าคุณจะต้องแก้ไขประเภทนี้เมื่อใดก็ตามที่คุณเพิ่มสัตว์ตัวใหม่และมีแนวโน้มว่าจะมีการทำซ้ำรหัสเล็กน้อยเพื่อให้มันทำงานได้

โอ้และในไม่กี่ภาษา (เช่น C #) public class Flier<T> : T {}นั้นผิดกฎหมาย มันจะไม่แม้แต่จะสร้าง ส่วนใหญ่ถ้าไม่ได้ทั้งหมดโปรแกรมเมอร์ OO คาดว่าจะยังคงเป็นFlier<Bird>Bird


ขอบคุณสำหรับความคิดเห็น ฉันมีความคิดเพิ่มเติม แต่เพียงเล็กน้อยแม้ว่าFlier<Bird>จะเป็นคอนเทนเนอร์ที่กำหนดไว้แล้ว แต่ก็ไม่มีใครคิดว่ามันจะเป็นBird(!?) List<String>คือรายการที่ไม่ใช่สตริง
Rob

@RobY - Flierไม่ใช่แค่คอนเทนเนอร์ ถ้าคุณคิดว่ามันเป็นแค่ตู้คอนเทนเนอร์ทำไมคุณถึงคิดว่ามันจะสามารถทดแทนการใช้มรดกได้?
Telastyn

ฉันทำเธอหายไปที่นั่น ... จุดของฉันคือ monad เป็นคอนเทนเนอร์ที่ปรับปรุงแล้ว Animal / Bird / Penguinมักเป็นตัวอย่างที่ไม่ดีเพราะมันนำมาซึ่งความหมายทุกประเภท เป็นตัวอย่างในทางปฏิบัติเป็น monad REST-ish เรากำลังใช้: Resource<String>.from(uri).get() Resourceเพิ่มความหมายด้านบนของString(หรือบางประเภทอื่น ๆ ) Stringดังนั้นจึงเห็นได้ชัดไม่ได้เป็น
Rob

@RobY - แต่แล้วมันก็ไม่เกี่ยวข้องกับการสืบทอดเช่นกัน
Telastyn

ยกเว้นว่าเป็นประเภทที่แตกต่างกัน ฉันสามารถใส่สตริงลงในทรัพยากรหรือฉันสามารถสรุปคลาส ResourceString และใช้การสืบทอด ความคิดของฉันคือการวางชั้นเรียนในคอนเทนเนอร์ที่ผูกมัดเป็นวิธีที่ดีกว่าสำหรับพฤติกรรมที่เป็นนามธรรมมากกว่าที่จะใส่ไว้ในลำดับชั้นของชั้นเรียนที่มีการสืบทอด ดังนั้น "ไม่มีทางที่เกี่ยวข้อง" ในแง่ของ "การแทนที่ / การทำลายล้าง" - ใช่
Rob
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.