في البرمجة الوظيفية، تُستخدم المونادات لتنظيم العمليات الحسابية كسلسلة من الخطوات، حيث تنتج كل خطوة قيمة بالإضافة إلى معلومات إضافية عن الحساب، مثل احتمالية الفشل، أو عدم الحتمية، أو الأثر الجانبي. بشكل أكثر رسمية، تُعرف المونادة بأنها باني نوع M مزود بعمليتين، return : <A>(a : A) -> M(A)
لرفع قيمة إلى السياق المونادي، وbind : <A,B>(m_a : M(A), f : A -> M(B)) -> M(B)
لربط العمليات المونادية. ببساطة، يمكن اعتبار المونادات على أنها واجهة مُطبقة على بُناة الأنواع، مما يسمح للدوال بالتعامل مع أنواع مختلفة من بُناة الأنواع التي تنفذ مفهوم المونادة (مثل Option
، وList
، وغيرها).[1][2]
يعود كل من مفهوم المونادة والمصطلح ذاته إلى نظرية الفئات، حيث تُعرف المونادة بأنها دالة ذات بنية إضافية.[ا][ب] أظهرت الأبحاث التي بدأت في أواخر الثمانينيات وأوائل التسعينيات أن المونادات يمكنها توحيد نماذج وظيفية لمشكلات حوسبية كانت تبدو متباينة. كما تقدم نظرية الفئات عددًا من المتطلبات الشكلية، المعروفة باسم قوانين المونادة، والتي يجب أن تفي بها أي مونادة ويمكن استخدامها للتحقق الشكلي من صحة الشيفرة المونادية.[3][4]
نظرًا لأن المونادات تُظهر الدلالة الشكلية لنوع معين من الحسابات، فيمكن استخدامها أيضًا لتنفيذ ميزات لغوية مريحة. بعض اللغات، مثل هاسكل، توفر تعريفات جاهزة ضمن مكتبة برمجية أساسية لبنية المونادة العامة والتطبيقات الشائعة لها.[1][5]
نظرة عامة
"بالنسبة لمونادة m
، فإن القيمة من النوع m a
تمثل الوصول إلى قيمة من النوع a
ضمن سياق المونادة." — سي. إيه. ماكان[6]
بشكل أدق، يمكن استخدام المونادة عندما لا يكون الوصول غير المقيَّد إلى قيمة ما مناسبًا لأسباب تتعلق بالسيناريو المحدد. في حالة مونادة مايبي، يعود السبب إلى أن القيمة قد لا تكون موجودة. وفي حالة مونادة IO، يعود السبب إلى أن القيمة قد لا تكون معروفة بعد، كما هو الحال عندما تمثل المونادة إدخال المستخدم الذي لن يتم توفيره إلا بعد عرض موجه. في جميع الحالات، يتم التقاط السيناريوهات التي يكون فيها الوصول منطقيًا من خلال عملية الربط المعرفة لتلك المونادة؛ فبالنسبة لمونادة مايبي، لا يتم ربط القيمة إلا إذا كانت موجودة، وبالنسبة لمونادة IO، لا يتم ربط القيمة إلا بعد تنفيذ العمليات السابقة في التسلسل.
يمكن إنشاء مونادة من خلال تعريف باني نوع M وعمليتين:
return :: a -> M a
(وغالبًا ما تُسمى أيضًا وحدة)، والتي تستقبل قيمة من النوعa
وتغلفها داخل "قيمة مونادية" من النوعM a
، وbind :: (M a) -> (a -> M b) -> (M b)
(ويُمثَّل عادةً بالرمز>>=
)، والتي تستقبل قيمة مونادية من النوعM a
ودالةf
تأخذ قيمًا من النوع الأساسيa
. تقوم bind بفك القيمةa
، وتُطبقf
عليها، وتعالج ناتجf
كقيمة موناديةM b
.
(هناك بناء بديل لكنه مكافئ يستخدم دالة join
بدلًا من عامل bind
، يمكن العثور عليه في القسم لاحقًا تحت عنوان § اشتقاق من المؤثرات.)
باستخدام هذه العناصر، يمكن للمبرمج إنشاء سلسلة من استدعاءات الدوال (أو "أنبوب معالجة") تربط معًا باستخدام عامل bind، حيث تُحوِّل كل دالة مدخلاتها من النوع العادي، ويتولى عامل bind التعامل مع القيمة المونادية الناتجة، ويمررها إلى الخطوة التالية في التسلسل.
عادةً، قد يحتوي عامل الربط >>=
على كود خاص بالمونادة يؤدي خطوات حسابية إضافية لا تتوفر داخل الدالة المُمررة كوسيط. بين كل زوج من استدعاءات الدوال المرتبطة، يمكن لـ bind إدخال معلومات إضافية في القيمة المونادية m a
، وهي معلومات لا تكون متاحة داخل الدالة f
، وتمريرها عبر سلسلة المعالجة. كما يمكنها أيضًا التحكم بدقة أكبر في تدفق التنفيذ، مثل استدعاء الدالة فقط في ظل ظروف معينة، أو تنفيذ الاستدعاءات بترتيب معين.
مثال: مونادة مايبي
أحد الأمثلة على المونادات هو النوع ربما أو مايبي Maybe
. فالقيم غير المعروفة هي إحدى المشكلات التي لا توفر العديد من لغات البرمجة الإجرائية أدوات محددة للتعامل معها، مما يتطلب استخدام نمط الكائن الفارغ (بالإنجليزية: null object pattern) أو التحقق من القيم غير الصالحة عند كل عملية للتعامل مع القيم غير المعرفة. هذا يسبب أخطاء ويصعّب بناء برامج قوية تتعامل مع الأخطاء بسلاسة. النوع Maybe
يجبر المبرمج على التعامل مع هذه النتائج غير المعرفة من خلال تحديد حالتين صريحتين للنتيجة: Just ⌑النتيجة⌑
، أو Nothing
. على سبيل المثال، قد يكون المبرمج بصدد بناء محلل يعيد نتيجة وسيطة، أو يشير إلى حالة يجب على المبرمج التعامل معها أيضاً. بقليل من الأدوات الدالية الإضافية، يتحول النوع Maybe
إلى موناد متكامل الميزات.[ج]:12.3 pages 148–151
في معظم اللغات، يُعرف موناد مايبي أيضاً بنوع الخيار (بالإنجليزية: option type)، وهو نوع يشير إلى ما إذا كانت القيمة موجودة أم لا. وعادةً ما يُعبّر عنه كنوع مُعَدَّد. في لغة الرست، يُعرف باسم Option<T>
ويمكن أن يحتوي على قيمة من النوع العام برمجة عامة T
، أو على المتغير الفارغ: None
.
// <T> يمثل نوعًا عامًا
enum Option<T> {
Some(T),
None,
}
يمكن أيضًا اعتبار Option<T>
كنوع "غلاف"، وهنا يأتي ارتباطه بالمونادات. في اللغات التي تحتوي على نوع مايبي، توجد دوال تساعد على استخدامه مثل تركيب دوال مونادية مع بعضها البعض، واختبار ما إذا كانت المايبي تحتوي على قيمة.
في المثال التالي، نستخدم مايبي كنتيجة لدوال قد تفشل، مثل حالة القسمة على صفر:
fn divide(x: Decimal, y: Decimal) -> Option<Decimal> {
if y == 0 { return None }
else { return Some(x / y) }
}
// divide(1.0, 4.0) -> يعيد Some(0.25)
// divide(3.0, 0.0) -> يعيد None
طريقة لاختبار ما إذا كانت مايبي تحتوي على قيمة هي استخدام عبارات if
:
let m_x = divide(3.14, 0.0);
if let Some(x) = m_x {
println!("الإجابة: {}", x)
} else {
println!("فشل القسمة، خطأ قسمة على صفر...")
}
بعض اللغات تدعم مطابقة النمط:
let result = divide(3.0, 2.0);
match result {
Some(x) => println!("الإجابة: {}", x),
None => println!("فشلت القسمة؛ المحاولة القادمة ستكون أفضل."),
}
يمكن للمونادات أن تُركّب دوال تُعيد مايبي، وأن تُجمِّعها معًا. مثال عملي: دالة تستقبل قيمًا من النوع مايبي وتُرجِع قيمة مايبي واحدة تكون لا شيء إذا كانت أي من القيم المُدخَلة هي لا شيء (Nothing).
fn chainable_division(maybe_x: Option<Decimal>, maybe_y: Option<Decimal>) -> Option<Decimal> {
match (maybe_x, maybe_y) {
(Some(x), Some(y)) => {
if y == 0 { return None }
else { return Some(x / y) }
},
_ => return None
}
}
chainable_division(chainable_division(Some(2.0), Some(0.0)), Some(1.0));
بدلاً من تكرار تعبيرات Some
، يمكن استخدام عامل يُعرف بـ "bind" (أو "map"، "flatmap"، أو "shove"[8]:2205s):
let maybe_x: Option<Decimal> = Some(1.0);
let maybe_result = maybe_x.map(add_one).map(decimal_to_string);
في Haskell، يوجد عامل "bind" (>>=
) لكتابة ذلك بشكل أنيق شبيه بـ تركيب الدوال:
halve :: Int -> Maybe Int
halve x
| even x = Just (x `div` 2)
| odd x = Nothing
halve x >>= halve
يمكن أيضًا تبسيط chainable_division
باستخدام دالة مجهولة (lambda):
chainable_division(mx, my) =
mx >>= (\x -> my >>= (\y -> Just (x / y)))
بالتالي، هذه هي الشروط اللازمة لتشكيل موناد:
- النوع المونادي
- مثل
Maybe
[ج]:148–151 - عملية الوحدة
- محول النوع مثل
Just(x)
[د]:93 - عملية الربط
- تجميع دوال مونادية مثل
>>=
أو.flatMap()
[ه]:150–151
كل المونادات تحتوي على هذه المكونات الثلاثة الأساسية، وقد تختلف في خواص إضافية، لكنها تشترك في هذه القاعدة.[1][9]
التعريف
التعريف الأكثر شيوعًا للموناد في البرمجة الدالية، كما استُخدم في المثال أعلاه، يستند في الواقع إلى ثلاثية كلايسلي [الإنجليزية] ⟨T، η، μ⟩ بدلاً من التعريف القياسي في نظرية الفئات. ومع ذلك، فإن البنيتين متكافئتان رياضيًا، لذا فإن أي تعريف منهما يُنتج مونادًا صالحًا. عند توفر نوعين أساسيين معرفين جيدًا T وU، فإن الموناد يتكوّن من ثلاثة أجزاء:
- منشئ نوع M يُكوّن نوعًا موناديًا M T[و]
- التحويل في c++, ويُطلق عليه غالبًا unit أو return، ويضمّن كائنًا x داخل الموناد:
- توليف، يُطلق عليه عادةً bind (كما في المتغير الحر والمتغير المقيد) ويُرمز له عادةً بالرمز تدوين وسطي
>>=
أو بطريقة تُدعى flatMap، يفك تغليف متغيّر مونادي ثم يُدخله في دالة/تعبير مونادي، منتجًا قيمة مونادية جديدة:
لكي يُعد البناء مونادًا كاملًا، يجب أن تحترم هذه الأجزاء الثلاثة عددًا من القوانين:
- unit هو عنصر محايد لـbind:
- unit هو أيضًا عنصر محايد أيمن لـbind:
- bind هو عملية تجميعية من حيث الأساس:[ط]
جبرِيًا، هذا يعني أن أي موناد يُنتج فئة (تُسمى فئة كلايسلي) و وحدية في فئة المؤثرات (من القيم إلى الحسابات)، حيث يكون التركيب المونادي هو العامل الثنائي في وحدانية[8]:2450s، وunit هو العنصر المحايد في هذه الوحدانية.
الاستخدام
تتجاوز قيمة نمط الموناد مجرد تكثيف الشيفرة وتوفير رابط إلى التفكير الرياضي. بغض النظر عن اللغة أو نمط برمجة الافتراضي الذي يستخدمه المطور، فإن اتباع نمط الموناد يجلب العديد من فوائد البرمجة الدالية البحتة. عن طريق تجسيد نوع معين من العمليات الحسابية، لا يقوم الموناد فقط بالتغليف التفاصيل المملة لهذا النمط من العمليات، بل يفعل ذلك بأسلوب برمجة تصريحية، مما يُحسّن من وضوح الشيفرة. نظرًا لأن القيم المونادية تمثّل بشكل صريح ليس فقط القيم المحسوبة، بل أيضًا الآثار المحسوبة، يمكن استبدال التعبير المونادي بقيمته وفق مبدأ شفافية مرجعية، تمامًا كما هو الحال مع التعبيرات البحتة، مما يسمح بالعديد من التقنيات والتحسينات القائمة على إعادة الكتابة.[4]
عادةً ما يستخدم المبرمجون bind لربط الدوال المونادية في تسلسل، مما دفع البعض إلى وصف المونادات بأنها "فواصل منقوطة قابلة للبرمجة"، في إشارة إلى الطريقة التي تستخدم بها العديد من لغات برمجة أمرية الفواصل المنقوطة للفصل بين العبارات.[1][5] ومع ذلك، فإن المونادات لا تقوم فعليًا بترتيب العمليات الحسابية؛ حتى في اللغات التي تُستخدم فيها كميزة أساسية، يمكن لتركيب الدوال الأبسط ترتيب الخطوات داخل البرنامج. تتمثل الفائدة العامة للموناد في تبسيط بنية البرنامج وتحسين فصل الاهتمامات من خلال التجريد.[4][11]
يمكن أيضًا رؤية بنية الموناد كنسخة رياضية فريدة ووقت التصريف من نموذج التصميم ديكور. بعض المونادات يمكنها تمرير بيانات إضافية غير متاحة للدوال، وبعضها يُمارس تحكمًا أدق في التنفيذ، كأن يستدعي دالة فقط في ظروف معينة. ونظرًا لأنها تتيح لمبرمجي التطبيقات تنفيذ منطق العمل مع تحميل الشيفرة المتكررة على وحدات مطورة مسبقًا، يمكن اعتبار المونادات أداة لبرمجة موجهة بالسمات.[12]
ومن الاستخدامات المهمة الأخرى للمونادات عزل الآثار الجانبية، مثل وحدات الإدخال والإخراج أو حالة قابلة للتغيير، في الشيفرة البحتة. حتى اللغات الدالية البحتة تستطيع تنفيذ هذه العمليات "غير البحتة" بدون استخدام المونادات، وذلك عبر مزيج معقد من تركيب الدوال والاستمرارية على وجه الخصوص.[2] لكن باستخدام المونادات، يمكن تجريد الكثير من هذا الهيكل، وذلك عن طريق أخذ كل نمط متكرر في شيفرة CPS وتجميعه في موناد مميز.[4]
إذا لم تكن اللغة تدعم المونادات بشكل افتراضي، فإنه لا يزال من الممكن تنفيذ النمط، وغالبًا دون صعوبة كبيرة. عند ترجمة بنية الموناد من نظرية الفئات إلى مصطلحات برمجية، تكون مفهومًا عامًا يمكن تعريفه مباشرةً في أي لغة تدعم ميزة مكافئة لتعدد الأشكال المقيد. قدرة المفهوم على البقاء غير مرتبط بالتفاصيل التشغيلية أثناء العمل على أنواع أساسية هي ميزة قوية، لكن الخصائص الفريدة والسلوك الصارم للمونادات يميزها عن المفاهيم الأخرى.[13]
التطبيقات
تركز مناقشات المونادات المحددة عادةً على حل مشكلة تنفيذ ضيقة النطاق، إذ إن كل موناد يمثل شكلًا حسابيًا معينًا. ومع ذلك، في بعض الحالات، يمكن للتطبيق أن يحقق أهدافه العامة من خلال استخدام المونادات المناسبة ضمن منطق العمل الأساسي له.
فيما يلي بعض التطبيقات التي تتخذ من المونادات جوهرًا لتصميمها:
- تستخدم مكتبة المحلل النحوي بارسك المونادات لدمج قواعد التجزئة بسيطة في قواعد أكثر تعقيدًا، وهي مفيدة بشكل خاص لـلغة مخصصة النطاق الصغيرة.[14]
- إكس موناد هو مدير نوافذ مبلط يتمحور حول بنية بيانات "زيبر"، والتي يمكن معالجتها كموناد في حالة خاصة من الاستمرارية المحددة.[15]
- يوفر الاستعلام التكميلي اللغوي من مايكروسوفت لغة استعلام لإطار دوت نت فريموورك، وهو مستوحى بشكل كبير من مفاهيم البرمجة الدالية، بما في ذلك عوامل أساسية لتركيب الاستعلامات باستخدام المونادات.[16]
- زيبر إف سي هو نظام ملفات بسيط وتجريبي، يستخدم أيضًا بنية "زيبر" بشكل أساسي لتنفيذ ميزاته.[17]
- يوفر إطار الامتدادات التفاعلية في جوهره واجهة (مونادية أو مزدوجة الموناد) للتعامل مع الجريان، ويُجسّد بذلك نمط المراقب.[18]
التاريخ
يعود استخدام مصطلح "موناد" في البرمجة إلى لغات البرمجة إيه بي إل وجيه، وهما تميلان إلى كونها وظيفية بحتة. ومع ذلك، فإن "الموناد" في هذه اللغات ليست سوى اختصار لدالة تأخذ مُعاملاً واحداً (بينما تُسمّى الدالة التي تأخذ معاملين "دياد"، وهكذا).[19]
كان عالم الرياضيات روجيه غوديمان أول من صاغ مفهوم الموناد (وسمّاه "البناء القياسي") في أواخر خمسينيات القرن العشرين، إلا أن المصطلح "موناد" الذي أصبح شائعاً لاحقاً نُشر بفضل عالم نظرية الفئات سوندرز ماكلين.[بحاجة لمصدر] ومع ذلك، فإن الصيغة المعرّفة أعلاه باستخدام bind، وُصفت أصلاً سنة 1965 بواسطة عالم الرياضيات هاينريش كلايسلي لإثبات أن أي موناد يمكن تمييزها على أنها اقتران بين دالتين متحدتي التغاير.[20]
بدءًا من ثمانينيات القرن العشرين، بدأ مفهوم نمط الموناد يظهر تدريجياً في مجتمع علوم الحاسوب. ووفقاً لباحث لغات البرمجة فيليب وادلر، فإن عالم الحاسوب جون س. رينولدز كان قد تنبأ بعدة جوانب من هذا المفهوم في سبعينيات وأوائل ثمانينيات القرن العشرين، عندما ناقش قيمة الاستمرارية، ونظرية الفئات كمصدر غني للدلالات الرسمية، والتمييز النمطي بين القيم والعمليات الحسابية.[4]
كما أن لغة البحث أوبال، التي استمر تطويرها حتى عام 1990، اعتمدت فعلياً إدخال/إخراج مبني على نوع مونادي، لكن العلاقة لم تُدرك في حينها.[21]
كان عالم الحاسوب أوجينيو موجي أول من ربط صراحة بين الموناد في نظرية الفئات والبرمجة الوظيفية، في ورقة مؤتمر عام 1989،[22] تلتها ورقة أكثر تطوراً في مجلة علمية سنة 1991. في أعمال سابقة، كان عدد من علماء الحاسوب قد قدموا نظرية الفئات كوسيلة لتوفير دلالات للتكامل اللامبداوي. أما البصيرة الرئيسية لدى موجي فكانت أن البرنامج الحقيقي في العالم الواقعي ليس مجرد دالة من القيم إلى قيم أخرى، بل هو تحويل يُشكّل "عمليات حسابية" على تلك القيم. وعند صياغته من منظور نظرية الفئات، يتبيّن أن المونادات هي البنية المناسبة لتمثيل هذه العمليات الحسابية.[3]
وقد ساهم آخرون في نشر هذا المفهوم وتوسيعه، منهم فيليب وادلر وسيمون بيتون جونز، وكلاهما كان مشاركاً في تحديد مواصفات لغة هاسكل. على وجه الخصوص، استخدمت هاسكل نموذج "تدفق كسول" إشكالي حتى الإصدار 1.2 للتوفيق بين الإدخال/الإخراج والتقييم الكسول، إلى أن تم التحول إلى واجهة مونادية أكثر مرونة.[23]
استمر مجتمع هاسكل في تطبيق المونادات على العديد من مشكلات البرمجة الوظيفية، وفي عقد 2010، أدرك الباحثون الذين يعملون بهاسكل في نهاية المطاف أن المونادات هي مرافق دالي للتطبيق أيضاً؛[24][ي]، وأن المونادات والأسهم كلاهما وحديات.[26]
في البداية، كانت البرمجة باستخدام المونادات مقتصرة إلى حد كبير على هاسكل وتفرعاتها، لكن مع تأثير البرمجة الوظيفية على أنماط البرمجة الأخرى، تبنت العديد من اللغات نمط الموناد (بروح المفهوم إن لم يكن بالاسم). توجد صيغ له الآن في سكيم، بيرل، بايثون، راكت، كلوجر، سكالا، إف شارب، وقد تم النظر في تضمينه ضمن معيار جديد للغة أم أل.
التحليل
إحدى فوائد نمط الموناد هي إدخال دقة رياضية على تركيب العمليات الحسابية. فيمكن استخدام قوانين الموناد ليس فقط للتحقق من صحة تطبيق معين، بل يمكن كذلك الاستفادة من الخصائص الموروثة من البُنى ذات الصلة (مثل الدوال المرافقة) من خلال الأنماط الفرعية.
التحقق من قوانين الموناد
بالعودة إلى مثال Maybe
، فقد تم إعلان مكوناته لتُشكّل موناد، ولكن لم يتم تقديم إثبات على أنها تفي بقوانين الموناد.
يمكن تصحيح ذلك بإدخال تفاصيل Maybe
في أحد طرفي القوانين العامة، ثم بناء سلسلة من المساوات الجبرية للوصول إلى الطرف الآخر:
القانون 1: eta(a) >>= f(x) ⇔ (Just a) >>= f(x) ⇔ f(a)
القانون 2: ma >>= eta(x) ⇔ ma
إذا كانت ma هي (Just a) فإن: eta(a) ⇔ Just a وإلا: Nothing ⇔ Nothing نهاية الشرط
القانون 3: ((ma >>= f(x)) >>= g(y)) ⇔ ma >>= (f(x) >>= g(y))
إذا كانت (ma >>= f(x)) هي (Just b) فإن: g(ma >>= f(x)) وإلا: Nothing نهاية الشرط
⇔ إذا كانت ma هي (Just a) و f(a) هي (Just b) فإن: (g ∘ f) a وإلا إذا كانت ma هي (Just a) و f(a) = Nothing فإن: Nothing وإلا: Nothing نهاية الشرط
الاشتقاق من الدوال المرافقة
على الرغم من ندرة ذلك في علوم الحاسوب، يمكن استخدام نظرية الفئات مباشرة، حيث تُعرّف الموناد على أنها دوال مع تحويلين طبيعيين إضافيين.[يا]
للبدء، يتطلب الأمر وجود دالة من الدرجة العليا (أو "وظيفية") تُسمى map لتأهيل البنية لتكون دالة مرافقة:
وهذا ليس دائمًا أمرًا صعبًا، خاصة عند اشتقاق الموناد من دالة مرافقة موجودة مسبقًا، حيث يرث الموناد الدالة map تلقائيًا. (ولأسباب تاريخية، تسمى هذه الدالة fmap
في لغة هاسكل.)
التحويل الأول في الموناد هو ذاته unit من ثلاثية كلايسلي، ولكن باتباع تسلسل البُنى، يتضح أن unit تُميز دالة مرافقة تطبيقية، وهي بنية وسيطة بين الموناد والدالة المرافقة الأساسية. في هذا السياق، يُشار إلى unit أحيانًا بـ pure لكنها تبقى نفس الدالة. ما يختلف في هذا البناء هو القانون الذي يجب أن تفي به unit؛ فبما أن bind غير معرف هنا، فإن القيد يُعطى من خلال map بدلًا منه:
تتمثل القفزة النهائية من الدالة التطبيقية إلى الموناد في التحويل الثاني، وهو دالة join (وفي نظرية التصنيف يُشار إليها غالبًا بالرمز μ)، والتي "تُسطّح" التطبيقات المتداخلة للموناد:
وبصفتها الدالة المميزة، يجب أن تحقق join أيضًا ثلاث صور من قوانين الموناد:
بغض النظر عمّا إذا كان المبرمج يعرف الموناد بشكل مباشر أو باستخدام ثلاثية كليزلي، فإن البنية الأساسية تظل نفسها، ويمكن اشتقاق الأشكال من بعضها بسهولة:
مثال آخر: القائمة
يُظهر موناد القائمة بشكل طبيعي كيف يمكن أن يكون اشتقاق الموناد من دالة تطبيقية أبسط أمرًا مفيدًا.
في العديد من لغات البرمجة، تأتي بنية القائمة معرفة مسبقًا مع بعض الميزات الأساسية، لذا يُفترض هنا أن منشئ النوع List
وعامل append (والذي يُكتب بصيغة ++
في الصيغة الترتيبية) متاحان مسبقًا.
إدراج قيمة بسيطة داخل قائمة هو أمر سهل في معظم اللغات:
unit(x) = [x]
من هذه النقطة، قد يبدو أن تطبيق دالة بشكل تكراري باستخدام اشتمال القائمة خيار سهل لـ bind وتحويل القوائم إلى موناد كامل. الصعوبة في هذا النهج هي أن bind يتوقع دوالًا مونادية، والتي تُنتج قوائم بحد ذاتها؛ ومع تكرار تطبيق الدوال، تتراكم طبقات من القوائم المتداخلة، مما يتطلب أكثر من مجرد فهم بسيط.
مع ذلك، فإن إجراء تطبيق أي دالة بسيطة على القائمة بأكملها، أي map، هو إجراء مباشر:
(map φ) xlist = [ φ(x1), φ(x2), ..., φ(xn) ]
الآن، هاتان العمليتان ترفّعان بالفعل List
إلى دالة تطبيقية.
ولكي تتأهل تمامًا كموناد، يتطلب الأمر فقط تعريفًا صحيحًا لـ join لتسطيح البنية المتكررة، ولكن بالنسبة للقوائم، فهذا يعني فقط إزالة التغليف الخارجي للقائمة ودمج القوائم الداخلية التي تحتوي على القيم:
join(xlistlist) = join([xlist1, xlist2, ..., xlistn]) = xlist1 ++ xlist2 ++ ... ++ xlistn
الموناد الناتج ليس مجرد قائمة، بل قائمة تُعيد تشكيل نفسها تلقائيًا وتُبسط مع تطبيق الدوال.
يمكن الآن اشتقاق bind أيضًا باستخدام صيغة فقط، ثم استخدامها لتمرير قيم List
خلال سلسلة من الدوال المونادية:

List
استخدام الدوال متعددة القيم، مثل الجذور المركبة.[29](xlist >>= f) = join ∘ (map f) xlist
أحد التطبيقات لهذا الموناد القائمي هو تمثيل الحوسبة غير الحتمية.
يمكن لـ List
أن تحتفظ بنتائج جميع المسارات التنفيذية في خوارزمية ما، ثم تُبسّط نفسها في كل خطوة لتنسى أي المسارات أدت إلى أي نتائج (وهو اختلاف مهم أحيانًا عن الخوارزميات الحتمية الكاملة).
فائدة أخرى هي أنه يمكن تضمين الفحوصات داخل الموناد؛ إذ يمكن تقليم مسارات محددة بطريقة شفافة عند أول نقطة فشل، دون الحاجة لإعادة كتابة الدوال في سلسلة التنفيذ.[28]
الموقف الثاني الذي يتألق فيه List
هو تركيب دالة متعددة القيم.
فعلى سبيل المثال، الجذر الـn لعدد ما يجب أن يُنتج n عددًا مركبًا مميزًا، ولكن إذا تم أخذ الجذر الـm لتلك النتائج، فإن القيم النهائية الـm•n يجب أن تتطابق مع ناتج الجذر الـm•n مباشرة.
يقوم List
بأتمتة هذه المشكلة بالكامل، مُبسّطًا النتائج من كل خطوة في قائمة مسطحة وصحيحة رياضيًا.[29]
تقنيات
توفر المونات تقنيات مثيرة تتجاوز مجرد تنظيم منطق البرامج. يمكن أن تمهد المونات الطريق لميزات تركيبية مفيدة، في حين تتيح طبيعتها الرياضية وعالية المستوى درجة كبيرة من التجريد.
السكر التركيبي
على الرغم من أن استخدام bind بشكل صريح غالبًا ما يكون منطقيًا، فإن العديد من المبرمجين يفضلون صياغة تشبه التعليمات الإمبراطية (وتُعرف باسم "السكر التركيبي" في هاسكل، و"تدوين الأداء" في اللغة كامل الموضوعية، و"التعبيرات الحسابية" في إف شارب،[30] و"للفهم" في سكالا). هذا مجرد تجميل لغوي يُخفي سلسلة موناتية خلف كتلة شيفرة، ويقوم المترجم لاحقًا بتحويل هذه التعبيرات بهدوء إلى كود وظيفي داخلي.
يمكن إظهار هذه الميزة من خلال تحويل دالة add
باستخدام Maybe
في هاسكل. الشكل غير الموناتي لـ add
في هاسكل يبدو هكذا:
add mx my =
case mx of
Nothing -> Nothing
Just x -> case my of
Nothing -> Nothing
Just y -> Just (x + y)
أما في هاسكل الموناتي، فإن return
هو الاسم القياسي لـ unit، ويجب التعامل مع تعبيرات لامبدا بشكل صريح، ولكن حتى مع هذه التفاصيل التقنية، فإن مونة Maybe
تسمح بتعريف أنظف:
add mx my =
mx >>= (\x ->
my >>= (\y ->
return (x + y)))
ومع استخدام السكر التركيبي، يمكن تبسيط ذلك أكثر إلى تسلسل واضح وسهل القراءة:
add mx my = do
x <- mx
y <- my
return (x + y)
مثال ثانٍ يوضح كيف يمكن استخدام Maybe
في لغة مختلفة تمامًا: F#.
باستخدام تعبيرات الحوسبة، يمكن كتابة دالة "قسمة آمنة" تُعيد None
عند وجود معامل غير صالح أو قسمة على صفر، كما يلي:
let readNum () =
let s = Console.ReadLine()
let succ,v = Int32.TryParse(s)
if (succ) then Some(v) else None
let secure_div =
maybe {
let! x = readNum()
let! y = readNum()
if (y = 0)
then None
else return (x / y)
}
وعند وقت البناء، سيقوم المترجم داخليًا "بتفكيك" هذا السكر التركيبي إلى سلسلة أكثر كثافة من استدعاءات bind:
maybe.Delay(fun () ->
maybe.Bind(readNum(), fun x ->
maybe.Bind(readNum(), fun y ->
if (y=0) then None else maybe.Return(x / y))))
وأخيرًا، حتى قوانين المونات العامة يمكن التعبير عنها باستخدام السكر التركيبي:
do { x <- return v; f x } == do { f v }
do { x <- m; return x } == do { m }
do { y <- do { x <- m; f x }; g y } == do { x <- m; y <- f x; g y }
الواجهة العامة
تحتاج كل مونة إلى تنفيذ محدد يستوفي قوانين المونات، ولكن الجوانب الأخرى مثل علاقتها بالبنى الأخرى أو العبارات الاصطلاحية القياسية في اللغة تكون مشتركة بين جميع المونات.
نتيجة لذلك، قد توفر لغة أو مكتبة برمجية واجهة عامة باسم Monad
تتضمن نموذج دالة|نماذج للدوال، وعلاقات التوريث، وحقائق عامة أخرى.
وبالإضافة إلى توفير بداية جاهزة للتطوير وضمان أن ترث المونة الجديدة خصائص من نوع أعلى (مثل الدوال المرافقة)، فإن التحقق من تصميم المونة مقابل الواجهة العامة يضيف طبقة إضافية من مراقبة الجودة.[بحاجة لمصدر]
المعاملات
يمكن غالبًا تبسيط الشيفرة الموناتية بشكل أكبر من خلال الاستخدام الحكيم للمعاملات.
يُعد الدال map مفيدًا بشكل خاص، لأنه يعمل مع أكثر من مجرد دوال موناتية مخصصة؛ فطالما أن دالة موناتية ينبغي أن تعمل بشكل مماثل لمُعامل معرف مسبقًا، يمكن استخدام map لـ"رفع" هذا المعامل البسيط إلى نظير موناتي مباشرة.[يب]
باستخدام هذه التقنية، يمكن تبسيط تعريف add
من مثال Maybe
إلى:
add(mx,my) = map (+)
ويمكن اتخاذ خطوة أبعد بتعريف add
ليس فقط لـMaybe
، بل لكل واجهة Monad
.
وبذلك، أي مونة جديدة تطابق الواجهة البنيوية وتنفذ دالة map الخاصة بها سترث تلقائيًا نسخة مرفوعة من add
.
التغيير الوحيد المطلوب للدالة هو تعميم توقيع النوع:
add : (Monad Number, Monad Number) → Monad Number
[31]
معامل موناتي آخر مفيد أيضًا في التحليل هو التركيب الموناتي (ويُمثل هنا بالصيغة >=>
)، والذي يتيح ربط الدوال الموناتية بأسلوب رياضي أكثر:
(f >=> g)(x) = f(x) >>= g
وباستخدام هذا المعامل، يمكن كتابة قوانين المونات بصيغة تعتمد على الدوال فقط، مما يبرز علاقتها بخاصيتي التجميع ووجود العنصر المحايد:
(unit >=> g) ↔ g
(f >=> unit) ↔ f
(f >=> g) >=> h ↔ f >=> (g >=> h)
[1]
وبالتالي، فإن ما سبق يوضح معنى كتلة "do" في هاسكل:
do _p <- f(x) _q <- g(_p) h(_q) ↔ (f >=> g >=> h)(x)
أمثلة إضافية
مونايد الهوية
أبسط أنواع المونايد هو مونايد الهوية، والذي يقوم فقط بوضع تعليقات توضيحية على القيم والدوال العادية ليتوافق مع قوانين المونايد:
'''newtype''' Id T = T unit(x) = x (x >>= f) = f(x)
لكن Identity
له استخدامات حقيقية، مثل توفير عودية للمحولات المونايدية التكرارية.
كما يمكن استخدامه أيضًا لتنفيذ تعيين متغيرات أساسية ضمن كتلة بأسلوب برمجة إجرائي.[يج][بحاجة لمصدر]
المجموعات
أي مجموعة تملك عملية append صحيحة تُعد مونويدًا بالفعل، لكن يتبين أن List
ليست النوع الوحيد من المجموعات التي تملك join معرفًا جيدًا وتُصنف كمونايد.
بل يمكن تحويل List
إلى مجموعات مونايدية أخرى بمجرد فرض خصائص خاصة على append:[يد][يه]
نوع المجموعة | خصائص المونويد | خصائص التوافقيات | |||
---|---|---|---|---|---|
تبادلي؟ | إديمبوتنت؟ | تفاصيل | مرتب؟ | عناصر فريدة؟ | |
List | لا | لا | مونويد حر | نعم | لا |
مجموعة متعددة محدودة | نعم | لا | لا | لا | |
مجموعة نهائية | نعم | نعم | لا | نعم |
مونايد الإدخال/الإخراج (هاسكل)
كما ذُكر سابقًا، لا يجب أن يحتوي الكود النقي على تأثيرات جانبية غير مُدارة، لكن ذلك لا يمنع البرنامج من وصف وإدارة التأثيرات بشكل صريح.
هذا المفهوم هو جوهر مونايد هاسكل المسمى IO، حيث يمكن اعتبار الكائن من النوع IO a
بمثابة وصف لفعل يُنفذ في العالم، مع إمكانية تقديم معلومات من نوع a
.
العملية التي لا تقدم أي معلومات عن العالم تكون من النوع IO ()
، وتُرجع القيمة الوهمية ()
.
عند ربط قيمة IO
بدالة، تقوم الدالة بحساب الفعل التالي الذي سيتم تنفيذه بناءً على معلومات العالم المقدمة من الفعل السابق (مثل إدخال المستخدم أو ملفات).
[23]
والأهم من ذلك، أن قيمة مونايد IO لا يمكن ربطها إلا بدالة تُرجع مونايد IO آخر، مما يفرض ترتيبًا صارمًا للأفعال، بحيث لا يتم تنفيذ إلا الأفعال المطلوبة، وبالترتيب المحدد.
على سبيل المثال، لدى هاسكل دوال تتعامل مع نظام الملفات، مثل واحدة تتحقق مما إذا كان الملف موجودًا، وأخرى تحذفه:
doesFileExist :: FilePath -> IO Bool
removeFile :: FilePath -> IO ()
الدالة الأولى تهتم بوجود الملف، وتُرجع نوع البيانات المنطقية داخل مونايد IO
،
أما الثانية فتهتم فقط بالفعل (الحذف) لذا تُرجع IO ()
فارغًا.
ولا يقتصر IO
على إدخال/إخراج الملفات فقط؛ بل يشمل إدخال/إخراج المستخدم أيضًا، ويمكن استخدامه لصنع برنامج أهلا بالعالم تقليدي:
main :: IO ()
main = do
putStrLn "Hello, world!"
putStrLn "What is your name, user?"
name <- getLine
putStrLn ("Nice to meet you, " ++ name ++ "!")
وعند إزالة السكر النحوي، يُترجم إلى سلسلة مونايدية كما يلي (>>
هو متغير لـbind عند تجاهل القيمة وإبقاء التأثير فقط):
main :: IO ()
main =
putStrLn "Hello, world!" >>
putStrLn "What is your name, user?" >>
getLine >>= (\name ->
putStrLn ("Nice to meet you, " ++ name ++ "!"))
مونايد الكاتب (جافا سكريبت)
من الحالات الشائعة الأخرى تسجيل ملف سجل أو تتبع تقدم البرنامج. أحيانًا يرغب المبرمج بتسجيل بيانات تقنية أكثر لأغراض تحليل الأداء أو التنقيح. مونايد الكاتب أو Writer
يُستخدم لتوليد مخرجات إضافية تتراكم خطوة بخطوة.
ولتوضيح أن نمط المونايد لا يقتصر على لغات البرمجة الدالية، يعرض هذا المثال مونايد Writer
باستخدام جافا سكريبت.
أولًا، تُستخدم مصفوفة (مع أطراف متداخلة) لبناء نوع Writer
كقائمة متصلة.
تُخزن القيمة الأساسية في الموقع 0 من المصفوفة، والموقع 1 يحمل سلسلة من الملاحظات الإضافية:
const writer = value => [value, []];
تعريف unit بسيط أيضًا:
const unit = value => [value, []];
ويكفي استخدام unit لتعريف دوال بسيطة تُخرج كائنات Writer
مع ملاحظات تنقيح:
const squared = x => [x * x, [`${x} was squared.`]];
const halved = x => [x / 2, [`${x} was halved.`]];
لكن المونايد الحقيقي يحتاج إلى bind، والذي في حالة Writer
يعني فقط دمج ناتج الدالة مع سلسلة الملاحظات:
const bind = (writer, transform) => {
const [value, log] = writer;
const [result, updates] = transform(value);
return [result, log.concat(updates)];
};
مثال على تكوين العمليات في موناد الكاتب
يمكن الآن ربط الدوال النموذجية باستخدام bind، ولكن تعريف نسخة من تركيب العمليات المونادية (تُسمى pipelog
هنا) يتيح تطبيق هذه الدوال بشكل أكثر إيجازًا:
const pipelog = (writer, ...transforms) =>
transforms.reduce(bind, writer);
والنتيجة النهائية هي فصل واضح بين مراحل الحساب وتسجيلها للمراجعة لاحقًا:
pipelog(unit(4), squared, halved);
// كائن writer الناتج = [8, ['4 was squared.', '16 was halved.']]
موناد البيئة
موناد البيئة (ويُسمى أيضًا "موناد القارئ" أو "موناد الدالة") يتيح تنفيذ عمليات تعتمد على قيم من بيئة مشتركة. المُنشئ النوعي للموناد يُحوّل النوع T إلى دوال من النوع E → T، حيث E هو نوع البيئة المشتركة. الدوال المونادية تكون:
بعض العمليات المفيدة في هذا السياق هي:
تُستخدم عملية ask لاسترجاع السياق الحالي، بينما تُستخدم local لتنفيذ عملية ضمن سياق فرعي مُعدل. ومثل موناد الحالة، يمكن تنفيذ العمليات في موناد البيئة بتوفير قيمة بيئة وتطبيقها على نسخة من الموناد.
من الناحية الشكلية، تُعادل القيمة في موناد البيئة دالةً ذات وسيط إضافي غير مُسمى؛ أما return وbind فتكافئان التراكبين K وS في حساب التراكب سكي.
موناد الحالة
يسمح موناد الحالة للمُبرمج بإرفاق معلومات حالة من أي نوع بعملية حسابية. لأي نوع قيمة معين، فإن النوع المقابل في موناد الحالة هو دالة تقبل حالة وتُخرج حالة جديدة (من النوع s
) بالإضافة إلى قيمة راجعة (من النوع t
). وهذا يشبه موناد البيئة، لكن مع إرجاع حالة جديدة، مما يسمح بنمذجة بيئة "قابلة للتغيير".
type State s t = s -> (t, s)
لاحظ أن هذا الموناد يأخذ وسيط نوع، هو نوع معلومات الحالة. تُعرف العمليات المونادية كما يلي:
-- "return" تُنتج القيمة المُعطاة دون تغيير الحالة.
return x = \s -> (x, s)
-- "bind" تُعدل m بحيث تُطبق f على نتيجتها.
m >>= f = \r -> let (x, s) = m r in (f x) s
عمليات الحالة المفيدة تشمل:
get = \s -> (s, s) -- فحص الحالة في هذه النقطة من الحساب.
put s = \_ -> ((), s) -- استبدال الحالة.
modify f = \s -> ((), f s) -- تحديث الحالة
عملية أخرى تطبق موناد الحالة على حالة ابتدائية معينة:
runState :: State s a -> s -> (a, s)
runState t s = t s
كتل "do" في موناد الحالة هي تسلسلات من العمليات التي يمكنها فحص وتحديث بيانات الحالة. بشكل غير رسمي، تُحوّل مونايد الحالة من نوع الحالة S نوع القيم المرجعية T إلى دوال من النوع ، حيث S هو نوع الحالة الأساسية. دوال return وbind تُعرّف كالتالي:
- .
من وجهة نظر نظرية التصنيف، يتم اشتقاق مونايد الحالة من الاقتران بين دالة الضرب ودالة الأس الأسي، والذي يوجد في أي فئة مغلقة ديكارتيًا حسب التعريف.
مونايد الاستمرارية
مونايد الاستمرارية[يو] بنوع مرجعي R تُحوّل النوع T إلى دوال من النوع . تُستخدم هذه المونايد لنمذجة الاستمرارية. ودوال return وbind كما يلي:
دالة نداء مع الاستمرار الحالي تُعرّف كما يلي:
تسجيل البرنامج
الكود التالي هو كود وهمي (بالإنجليزية: pseudocode). لنفترض أن لدينا دالتين foo
وbar
، بأنواع:
foo : int -> int
bar : int -> int
أي أن كلا الدالتين تأخذ عددًا صحيحًا وتعيد عددًا صحيحًا آخر. يمكننا حينها تطبيق الدالتين بالتتابع بهذا الشكل:
foo (bar x)
والناتج هو نتيجة تطبيق foo
على نتيجة bar
المطبقة على x
.
لكن، إذا كنا نقوم بتصحيح الأخطاء في البرنامج، ونرغب بإضافة رسائل تسجيل إلى foo
وbar
، فسنغير التواقيع لتصبح:
foo : int -> int * string bar : int -> int * string
بحيث تُعيد كل دالة زوجًا يحتوي على نتيجة التطبيق (عدد صحيح)، ورسالة تسجيل تحتوي على معلومات حول الدالة المطبقة والدوال السابقة.
لكن هذا يعني أننا لم نعد قادرين على تركيب الدوال foo
وbar
، لأن نوع الإدخال int
لا يتوافق مع نوع الإخراج int * string
. ورغم أنه يمكننا استعادة إمكانية التركيب بتعديل أنواع كل دالة إلى int * string -> int * string
، إلا أن هذا سيتطلب منا كتابة كود متكرر في كل دالة لاستخراج العدد من الزوج، وهذا قد يصبح مرهقًا مع ازدياد عدد هذه الدوال.
بدلًا من ذلك، لنقم بتعريف دالة مساعدة لتجريد هذا التكرار:
bind : int * string -> (int -> int * string) -> int * string
الدالة bind
تأخذ زوجًا من عدد صحيح وسلسلة نصية، ثم تأخذ دالة (مثل foo
) تُحوّل عددًا صحيحًا إلى زوج عدد وسلسلة. ناتجها هو زوج عدد وسلسلة، وهو نتيجة تطبيق الدالة على العدد الموجود داخل الزوج الأصلي. بهذه الطريقة، نكتب الكود التكراري مرة واحدة فقط في bind
.
وهكذا نستعيد بعض القدرة على التركيب. على سبيل المثال:
bind (bind (x,s) bar) foo
حيث (x,s)
هو زوج عدد صحيح وسلسلة نصية.[يز]
لإيضاح الفوائد أكثر، لنُعرّف عاملًا (operator) بديلاً لدالة bind
:
(>>=) : int * string -> (int -> int * string) -> int * string
بحيث يصبح t >>= f
مساويًا لـbind t f
.
وبذلك يصبح المثال السابق:
((x,s) >>= bar) >>= foo
وأخيرًا، نُعرّف دالة جديدة لتجنب كتابة (x, "")
كل مرة نريد فيها إنشاء رسالة تسجيل فارغة:
return : int -> int * string
الذي يلف x
في الزوج المرتب الموضح أعلاه.
النتيجة هي سلسلة لمعالجة تسجيل الرسائل:
((return x) >>= bar) >>= foo
وهذا يسمح لنا بتسجيل تأثيرات bar
وfoo
على x
بسهولة أكبر.
int * string
تمثل قيمة مونادية مشفرة بشكل تقريبي.[يز] الدالتان bind
وreturn
تشبهان الدوال التي تحمل نفس الأسماء في الموناديات.
في الواقع، int * string
وbind
وreturn
تشكل مونادًا.
المونادات الجمعّية
الموناد الجمعّي هو موناد مزود بعملية ثنائية إضافية مغلقة، إبدالية وترابطية تُسمى mplus وعنصر محايد تحتها يُسمى mzero.
يمكن اعتبار موناد Maybe
جمعيًا، حيث يكون Nothing
هو mzero وتغييرة على عامل فصل منطقي كـmplus.
وأيضًا List
هو موناد جمعي، حيث القائمة الفارغة []
تعمل كـmzero وعامل الربط ++
يعمل كـmplus.
بديهيًا، يمثل mzero غلافًا موناديًا لا يحتوي على قيمة من النوع الأساسي، ولكنه يعتبر "صفرًا" (بدلًا من "واحد") لأنه يعمل كعنصر ماص لـbind، حيث يعيد mzero كلما تم ربطه بدالة مونادية. هذه الخاصية ثنائية الجانب، فـbind سيعيد أيضًا mzero عندما يتم ربط أي قيمة بـموناد 0 (عدد).
من الناحية النظرية للفئات، يؤهل الموناد الجمعي ليكون مرة كمونويد على الدوال المونادية مع bind (كما تفعل جميع المونادات)، ومرة أخرى على القيم المونادية عبر mplus.[32][يح]
المونادات الحرة
أحيانًا قد يكون المخطط العام للموناد مفيدًا، لكن لا يوجد نمط بسيط يوصي بموناد معين. هنا يأتي دور الموناد الحر; ككائن حر في فئة المونادات، يمكنه تمثيل بنية مونادية دون قيود محددة بخلاف قوانين الموناد نفسها. كما يربط المونويد الحر العناصر دون تقييم، يسمح الموناد الحر بربط الحسابات مع علامات لتلبية نظام الأنواع، لكنه لا يفرض دلالات أعمق.
على سبيل المثال، بالعمل فقط من خلال العلامتين Just
وNothing
، فإن موناد Maybe
هو في الحقيقة موناد حر.
أما موناد List
، فلا يعد مونادًا حرًا لأنه يحمل حقائق إضافية محددة عن القوائم (مثل append).
مثال آخر هو موناد حر مجرد:
data Free f a
= Pure a
| Free (f (Free f a))
unit :: a -> Free f a
unit x = Pure x
bind :: Functor f => Free f a -> (a -> Free f b) -> Free f b
bind (Pure x) f = f x
bind (Free x) f = Free (fmap (\y -> bind y f) x)
ومع ذلك، ليست المونادات الحرة مقصورة على قائمة مرتبطة كما في هذا المثال، بل يمكن بناؤها حول هياكل أخرى مثل الشجرة.
قد يبدو استخدام المونادات الحرة غير عملي في البداية، لكن طبيعتها الرسمية مناسبة جدًا للمشاكل النحوية. يمكن استخدام الموناد الحر لتتبع النحو والنوع مع ترك الدلالات لوقت لاحق، وقد وجد استخدامًا في المحللات والمفسرات.[33] وقد طبقها آخرون على مشاكل تشغيلية وديناميكية أكثر، مثل توفير المُكررون داخل لغة برمجة.[34]
الكومونادات
إلى جانب توليد المونادات مع خصائص إضافية، يمكن لأي موناد معين أيضًا تعريف كوموناد. مفهوميًا، إذا كانت المونادات تمثل حسابات مبنية على قيم أساسية، فيمكن اعتبار الكومونادات تقليصات تعود إلى القيم. الكود المونادي، بمعنى ما، لا يمكن "فك تغليفه" بالكامل؛ فبمجرد أن تُلف القيمة داخل موناد، تبقى محصورة هناك مع أي تأثيرات جانبية (وهذا أمر جيد في البرمجة الوظيفية الصرفة). لكن أحيانًا تكون المشكلة أكثر عن استهلاك بيانات سياقية، وهو ما يمكن للكومونادات نمذجته بشكل صريح.
تقنيًا، الكوموناد هو الثنائي التصنيفي للموناد، مما يعني بشكل غير رسمي أنه يحتوي على نفس المكونات المطلوبة، ولكن مع عكس اتجاه توقيعات الأنواع. ابتداءً من تعريف الموناد المرتكز على bind، يتكون الكوموناد من:
- منشئ نوع W الذي يحدد النوع الأعلى W T
- عكس unit، ويسمى هنا counit، ويستخرج القيمة الأساسية من الكوموناد:
counit(wa) : W T → T
- عكس bind (يُمثّل أيضًا بـ
=>>
) الذي يمدد سلسلة الدوال المختزلة:[يط]
(wa =>> f) : (W U, W U → T) → W T
يجب أن تحقق extend وcounit أيضًا قوانين ثنائية للموناد:
counit ∘ '''(''' (wa =>> f) → wb ''')''' ↔ f(wa) → b wa =>> counit ↔ wa wa '''(''' (=>> f(wx = wa)) → wb (=>> g(wy = wb)) → wc ''')''' ↔ '''(''' wa (=>> f(wx = wa)) → wb ''')''' (=>> g(wy = wb)) → wc
مماثلًا للمونادات، يمكن أيضًا اشتقاق الكومونادات من الفانكتورات باستخدام عكس join:
- duplicate تأخذ قيمة كومونادية موجودة وتلفها بطبقة أخرى من البنية الكومونادية:
duplicate(wa) : W T → W (W T)
بينما تُعكس عمليات مثل extend، إلا أن الكوموناد لا يعكس الدوال التي يعمل عليها، ونتيجة لذلك، فإن الكومونادات لا تزال فانكتورات مع map، وليست دال (رياضيات)ات. التعريف البديل باستخدام duplicate وcounit وmap يجب أن يحترم قوانينه الخاصة للكوموناد:
((map duplicate) ∘ duplicate) wa ↔ (duplicate ∘ duplicate) wa ↔ wwwa ((map counit) ∘ duplicate) wa ↔ (counit ∘ duplicate) wa ↔ wa ((map map φ) ∘ duplicate) wa ↔ (duplicate ∘ (map φ)) wa ↔ wwb
وكما هو الحال مع المونادات، يمكن تحويل الشكلين تلقائيًا:
(map φ) wa ↔ wa =>> (φ ∘ counit) wx duplicate wa ↔ wa =>> wx wa =>> f(wx) ↔ ((map f) ∘ duplicate) wa
مثال بسيط هو كوموناد المنتج، الذي ينتج قيمًا بناءً على قيمة إدخال وبيانات بيئة مشتركة.
في الواقع، كوموناد Product
هو ببساطة العكس للكوموناد Writer
وفعليًا مماثل لكوموناد Reader
(كلاهما مذكور أدناه).
يختلف Product
وReader
فقط في توقيعات الدوال التي يقبلانها، وكيف يكملان تلك الدوال بتغليف أو فك تغليف القيم.
مثال أقل بساطة هو كوموناد التدفق، الذي يمكن استخدامه لتمثيل الجريان وربط فلاتر بالإشارات الواردة باستخدام extend. في الحقيقة، رغم أن الكومونادات ليست شائعة مثل المونادات، فقد وجد الباحثون أنها مفيدة بشكل خاص لتحليل التدفق ونمذجة برمجة تنقل المعطيات.
لكن بسبب تعريفها الصارم، لا يمكن ببساطة تحريك الكائنات ذهابًا وإيابًا بين المونادات والكومونادات. كتجريد أعلى، يمكن أن تضمّن الأسهم كلا الهيكلين، لكن إيجاد طرق أكثر تفصيلًا لدمج كود الموناد والكوموناد هو مجال بحث نشط.
انظر أيضًا
بدائل لنمذجة العمليات الحسابية:
- أنظمة التأثير (وخاصة معالجات التأثير الجبري) هي طريقة مختلفة لوصف التأثيرات الجانبية على شكل أنواع
- نوع التفرد هو نهج ثالث لمعالجة التأثيرات الجانبية في اللغات الوظيفية
مفاهيم تصميم ذات صلة:
- البرمجة الموجهة بالسمات تركز على فصل الشيفرات المساعدة والثانوية لتحسين التركيبية والبساطة
- عكس التحكم هو مبدأ تجريدي لاستدعاء دوال معينة من إطار عمل شامل
- صنف نوعي هو ميزة لغوية محددة تُستخدم لتنفيذ المونادات وبُنى أخرى في هاسكل
- نموذج التصميم ديكور هو وسيلة أكثر واقعية ومرتجلة لتحقيق فوائد مماثلة في البرمجة الكائنية التوجه
تعميمات المونادات:
- الدالة التطبِيقية تعمم من المونادات من خلال الاحتفاظ فقط بـ unit والقوانين المرتبطة بها مع map
- السهم يستخدم بُنى إضافية لدمج الدوال العادية والمونادات ضمن واجهة موحدة
- محوّل الموناد يعمل على مونادات منفصلة لدمجها بشكل تركيبي
الملاحظات
- ^ بشكل أكثر دقة، المونادة هي وحدية في فئة الدوال.
- ^ نظرًا لأن الدوال التي تتعامل مع عدة المتغير الحر والمتغير المقيدات شائعة في البرمجة، فإن المونادات كما وردت في هذه المقالة تُعد تقنيًا ما يسميه منظّرو الفئات المونادات القوية.[3]
- ^ ا ب يمكن العثور على الدافع المحدد لـ "مايبي" في (هوتون 2016).[7]
- ^ اكتب عنوان المرجع بين علامتي الفتح
<ref>
والإغلاق</ref>
للمرجعgHutton2ndJust
- ^ اكتب عنوان المرجع بين علامتي الفتح
<ref>
والإغلاق</ref>
للمرجعgHutton2ndBind
- ^ دلاليًا، M ليس بسيطًا بل يُمثّل دال (رياضيات) على فئة (رياضيات) جميع القيم المنمطّة جيدًا:
- ^ بينما هي دالة (متعددة الأشكال برمجياً)، فإن unit (وغالبًا ما تُسمى η في نظرية الفئات) تُعد رياضيًا تحويل طبيعي، يربط بين المؤثرات:
- ^ bind، من ناحية أخرى، ليس تحويل طبيعي في نظرية الفئات، بل هو امتداد يقوم بـرفع تحويل (من القيم إلى الحسابات) إلى تحويل بين الحسابات:
- ^ بدقة، قد لا تكون bind تجميعية رسميًا في كل السياقات لأنها تقابل التطبيق داخل تكامل لامدا، لا الرياضيات. في حساب λ الصارم، قد يتطلب تقييم bind أولًا تغليف الحد الأيمن (عند ربط قيمتين موناديتين) أو العملية نفسها (بين دالتين موناديتين) داخل دالة مجهولة حتى تظل تستقبل الإدخال من الطرف الأيسر.[10]
- ^ بحلول إصدار GHC 7.10.1، بدأت هاسكل بتنفيذ اقتراح "الموناد التطبيقي" (AMP) الذي يتطلب إضافة 7 أسطر من الشيفرة إلى أي وحدة تستخدم المونادات.[25]
- ^ غالبًا ما يُشار إلى هذه التحويلات الطبيعية في الرياضيات كتحويلات η و μ، أي: η تمثل "الوحدة" و μ تمثل "الدمج" أو "الضم".
- ^ بعض اللغات مثل Haskell توفر اسمًا مستعارًا لـmap في سياقات أخرى باسم
lift
، إلى جانب نسخ متعددة لعدد مختلف من المعاملات، وهي تفاصيل تُغفل هنا. - ^ في نظرية الفئات، يمكن أيضًا اعتبار مونايد
Identity
ناتجًا عن اقتران لأي دالة تتابعية مع معكوسها. - ^ ترى نظرية الفئات هذه المونايدات الجمعية كاقترانات بين الدالة التتابعية الحرة ودوال مختلفة من فئة المجموعات إلى فئة المونويدات.
- ^ هنا، تكون مهمة المبرمج إنشاء مونويد مناسب، أو ربما اختيار مونويد من مكتبة.
- ^ قد يرغب القارئ في متابعة خيط مَكان [6] ومقارنته بالأنواع أدناه.
- ^ ا ب في هذه الحالة، قامت
bind
بـ"إلصاق" سلسلة نصية حيث كان يوجد سابقًا عدد فقط؛ أي أن المبرمج أنشأ ترابطًا: زوج(x,s)
، المشار إليه بـint * string
في الكود الوهمي § أعلاه. - ^ جبرية، العلاقة بين الجانبين (غير الإبداليين) للمونويد تشبه علاقة شبه-حلقات قريبة، وبعض المونادات الجمعّية تؤهل لذلك. ولكن ليس كل المونادات الجمعّية تفي بقوانين توزيعية حتى لشبه-حلقة قريبة.[32]
- ^ في هاسكل، extend يُعرف فعليًا مع تبديل المدخلات، ولكن بما أن الكاريّنج غير مستخدم في هذا المقال، فهو يُعرف هنا كعكس دقيق لـbind.
المراجع
- ^ ا ب ج د ه و O'Sullivan، Bryan؛ Goerzen، John؛ Stewart، Don (2009). "المونادات". Real World Haskell. سيباستوبول، كاليفورنيا: O'Reilly Media. الفصل 14. ISBN:978-0596514983. مؤرشف من الأصل في 2025-04-24.
- ^ ا ب Wadler، Philip (يونيو 1990). "Comprehending Monads". مؤتمر ACM حول لغة LISP والبرمجة الوظيفية. نيس، فرنسا. CiteSeerX:10.1.1.33.5381.
- ^ ا ب ج Moggi، Eugenio (1991). "Notions of computation and monads" (PDF). Information and Computation. ج. 93 ع. 1: 55–92. CiteSeerX:10.1.1.158.5275. DOI:10.1016/0890-5401(91)90052-4. مؤرشف من الأصل (PDF) في 2018-10-11.
- ^ ا ب ج د ه Wadler، Philip (يناير 1992). "The essence of functional programming". الندوة السنوية التاسعة عشرة لـ ACM حول مبادئ لغات البرمجة. ألبوكيركي، نيو مكسيكو. CiteSeerX:10.1.1.38.9516.
- ^ ا ب Hudak، Paul؛ Peterson، John؛ Fasel، Joseph (1999). "حول المونادات". A Gentle Introduction to Haskell 98. الفصل 9. مؤرشف من الأصل في 2025-03-16.
- ^ ا ب إجابة C. A. McCann (23 يوليو 2010): كيف ولماذا تعمل مونادة Cont في هاسكل؟ نسخة محفوظة 2024-10-01 على موقع واي باك مشين.
- ^ Graham Hutton (2016) Programming in Haskell 2nd Edition
- ^ ا ب Beckerman، Brian (21 نوفمبر 2012). "Don't fear the Monad". يوتيوب. مؤرشف من الأصل في 2024-12-17.
- ^ Spivey، Mike (1990). "A functional theory of exceptions" (PDF). Science of Computer Programming. ج. 14 ع. 1: 25–42. DOI:10.1016/0167-6423(90)90056-J. مؤرشف من الأصل (PDF) في 2025-01-19.
- ^ "Monad laws". HaskellWiki. haskell.org. اطلع عليه بتاريخ 2018-10-14.
- ^ "What a Monad is not". 7 أكتوبر 2018.
- ^ De Meuter، Wolfgang (1997). "Monads as a theoretical foundation for AOP" (PDF). International Workshop on Aspect Oriented Programming at ECOOP. Jyväskylä, Finland. CiteSeerX:10.1.1.25.8262. مؤرشف من الأصل (PDF) في 2025-04-19.
- ^ "Monad (sans metaphors)". HaskellWiki. 1 نوفمبر 2009. مؤرشف من الأصل في 2025-03-17. اطلع عليه بتاريخ 2018-10-24.
- ^ O'Sullivan، Bryan؛ Goerzen، John؛ Stewart، Don (2009). "Using Parsec". Real World Haskell. Sebastopol, California: O'Reilly Media. chapter 16. ISBN:978-0596514983. مؤرشف من الأصل في 2025-04-24.
- ^ Stewart، Don (17 مايو 2007). "Roll Your Own Window Manager: Tracking Focus with a Zipper". Control.Monad.Writer. مؤرشف من الأصل في 2018-02-20. اطلع عليه بتاريخ 2018-11-19.
- ^ Benton، Nick (2015). "Categorical Monads and Computer Programming" (PDF). London Mathematical Society Impact150 Stories. ج. 1. مؤرشف من الأصل (PDF) في 2024-11-13. اطلع عليه بتاريخ 2018-11-19.
- ^ Kiselyov، Olag (2007). "Delimited Continuations in Operating Systems". Modeling and Using Context. Lecture Notes in Computer Science. Springer Berlin Heidelberg. ج. 4635. pages 291--302. DOI:10.1007/978-3-540-74255-5_22. ISBN:978-3-540-74255-5.
- ^ Meijer، Erik (27 مارس 2012). "Your Mouse is a Database". ACM Queue. ج. 10 ع. 3: 20–33. DOI:10.1145/2168796.2169076.
- ^ Iverson، Kenneth (سبتمبر 1987). "A dictionary of APL". APL Quote Quad. ج. 18 ع. 1: 5–40. DOI:10.1145/36983.36984. ISSN:1088-6826. S2CID:18301178. مؤرشف من الأصل في 2025-03-16. اطلع عليه بتاريخ 2018-11-19.
- ^ Kleisli، Heinrich (1965). "Every standard construction is induced by a pair of adjoint functors" (PDF). Proceedings of the American Mathematical Society. ج. 16 ع. 3: 544–546. DOI:10.1090/S0002-9939-1965-0177024-4. مؤرشف من الأصل (PDF) في 2025-01-19. اطلع عليه بتاريخ 2018-11-19.
- ^ Peter Pepper، المحرر (نوفمبر 1997). The Programming Language Opal (Technical report) (ط. 5th corrected). Fachbereich Informatik, Technische Universität Berlin. CiteSeerX:10.1.1.40.2748.
- ^ Moggi، Eugenio (يونيو 1989). "Computational lambda-calculus and monads" (PDF). Fourth Annual Symposium on Logic in computer science. Pacific Grove, California. CiteSeerX:10.1.1.26.2787. مؤرشف من الأصل (PDF) في 2018-09-24.
- ^ ا ب Peyton Jones، Simon L.؛ Wadler، Philip (يناير 1993). "Imperative functional programming" (PDF). 20th Annual ACM Symposium on Principles of Programming Languages. Charleston, South Carolina. CiteSeerX:10.1.1.53.2504. مؤرشف من الأصل (PDF) في 2025-03-15.
- ^ Brent Yorgey Typeclassopedia نسخة محفوظة 2025-04-29 على موقع واي باك مشين.
- ^ Stack overflow (8 Sep 2017) Defining a new monad in haskell raises no instance for Applicative نسخة محفوظة 2025-04-22 على موقع واي باك مشين.
- ^ Brent Yorgey Monoids نسخة محفوظة 2025-04-29 على موقع واي باك مشين.
- ^ "دالة مرافقة تطبيقية". HaskellWiki. Haskell.org. 7 مايو 2018. مؤرشف من الأصل في 2018-10-30. اطلع عليه بتاريخ 2018-11-20.
- ^ ا ب Gibbard، Cale (30 ديسمبر 2011). "Monads as containers". HaskellWiki. Haskell.org. مؤرشف من الأصل في 2017-12-14. اطلع عليه بتاريخ 2018-11-20.
- ^ ا ب Piponi، Dan (7 أغسطس 2006). "You Could Have Invented Monads! (And Maybe You Already Have.)". A Neighborhood of Infinity. مؤرشف من الأصل في 2018-10-24. اطلع عليه بتاريخ 2018-10-16.
- ^ "Some Details on F# Computation Expressions". 21 سبتمبر 2007. مؤرشف من الأصل في 2019-01-30. اطلع عليه بتاريخ 2018-10-09.
- ^ Giles، Brett (12 أغسطس 2013). "Lifting". HaskellWiki. Haskell.org. مؤرشف من الأصل في 2018-01-29. اطلع عليه بتاريخ 2018-11-25.
- ^ ا ب Rivas، Exequiel؛ Jaskelioff، Mauro؛ Schrijvers، Tom (يوليو 2015). "From monoids to near-semirings: the essence of MonadPlus and Alternative" (PDF). 17th International ACM Symposium on Principles and Practice of Declarative Programming. Siena, Italy. CiteSeerX:10.1.1.703.342. مؤرشف من الأصل (PDF) في 2025-01-19.
- ^ Swierstra، Wouter (2008). "Data types à la carte" (PDF). Functional Pearl. Journal of Functional Programming. Cambridge University Press. ج. 18 ع. 4: 423–436. CiteSeerX:10.1.1.101.4131. DOI:10.1017/s0956796808006758 (غير نشط 1 نوفمبر 2024). ISSN:1469-7653. S2CID:21038598. مؤرشف من الأصل (PDF) في 2021-10-26.
{{استشهاد بدورية محكمة}}
: صيانة الاستشهاد: وصلة دوي غير نشطة منذ 2024 (link) - ^ Kiselyov، Oleg (مايو 2012). "Iteratees" (PDF). في Schrijvers، Tom؛ Thiemann، Peter (المحررون). International Symposium on Functional and Logic Programming. Lecture Notes in Computer Science. Kobe, Japan: Springer-Verlag. ج. 7294. ص. 166–181. DOI:10.1007/978-3-642-29822-6_15. ISBN:978-3-642-29822-6. مؤرشف من الأصل (PDF) في 2025-03-12.
وصلات خارجية
مراجع من هاسكل ويكي:
- "كل شيء عن المونادات" (أصلًا من تأليف جيف نيوبرن) — مناقشة شاملة لجميع المونادات الشائعة وكيفية عملها في هاسكل؛ تتضمن تشبيه "خط التجميع الآلي".
- "موسوعة الأصناف النوعية" (أصلًا من تأليف برينت يورجي) — شرح مفصل حول كيفية ترابط الأصناف النوعية الأساسية في هاسكل، بما في ذلك المونادات.
دروس تعليمية:
- "حفنة من المونادات" (من كتاب هاسكل الإلكتروني تعلم هاسكل من أجل الخير العظيم! — فصل يُقدم المونادات انطلاقًا من صنفي الدوال: "فنكتر" و"تطبيقية"، مع أمثلة.
- "من أجل المزيد من المونادات" — فصل ثانٍ يشرح مزيدًا من التفاصيل والأمثلة، بما في ذلك موناد
الاحتمالية
لسلاسل سلسلة ماركوف. - "الدوال، التطبيقية، والمونادات بالصور" (بقلم أديتيا بهارجافا) — درس تعليمي سريع، فكاهي، ومرئي عن المونادات.
حالات مثيرة للاهتمام:
- "أنابيب يونكس كمونادات IO" (بقلم أوليغ كيسليوف) — مقالة قصيرة تشرح كيف أن أنابيب يونكس تُعد فعليًا مونادية.
- برمجة سكالا الاحترافية: أنماط تصميم مونادية للويب (بقلم غريغوري ميريديث) — مخطوطة كاملة غير منشورة حول كيفية تحسين العديد من جوانب تطوير الويب في سكالا (لغة برمجة) باستخدام المونادات.