مرحبا بالجميع.
بعد أربع سنوات من البرمجة في Scala ، نما
فهمتي للـ monads أخيرًا إلى النقطة التي يمكنك من خلالها شرحها للآخرين دون الرجوع إلى نظرية الفئة و
monad الكلاسيكية
- إنه مجرد monoid في فئة endofunction ، التي تخيف المبرمجين ليس أسوأ من صراصير dichlorvos.
سيتم كتابة أمثلة التعليمات البرمجية في Kotlin ، كما أنها تحظى بشعبية كبيرة ، وفي الوقت نفسه وظيفية للغاية (في كل من حواس الكلمة).
دعنا نبدأ مع مفهوم
functor ، هنا هو:
interface Functor<A>
ما هو معناها؟ إن functor هو عبارة عن تجريد من حساب تعسفي يقوم بإرجاع نتيجة من النوع A. نتجاهل كيفية إنشاء functor جديد ، والأهم من ذلك ، كيفية حساب قيمته A. على وجه الخصوص ، يمكن إخفاء دالة خلف واجهة functor مع عدد تعسفي من الحجج ، وليس بالضرورة وظيفة نقية.
أمثلة على تطبيقات functor:
- ثابت
- تعمل مع عدد اعتباطي من الوسائط التي ترجع نتيجة نوع
A
- مولد الحالة شبه العشوائية (عشوائي)
- مولد رقم عشوائي للأجهزة
- قراءة كائن من القرص أو من الشبكة
- حساب غير متزامن - يتم تمرير رد اتصال إلى تنفيذ functor ، والذي سيتم استدعاؤه في وقت لاحق
كل هذه الأمثلة ، باستثناء الثابت ، لها خاصية واحدة مهمة - فهي كسولة ، أي لا يحدث الحساب نفسه عند إنشاء العامل ، ولكن عندما يتم حسابه.
لا تسمح واجهة
Functor<A>
إما بالحصول على قيمة من النوع
A
من
Functor<A>
، أو إنشاء
Functor<A>
جديد
Functor<A>
من قيمة موجودة من النوع
A
ولكن حتى في ظل هذه القيود ، فإن المُشغل ليس عديم الفائدة - إذا كان في بعض الأنواع
B
يمكننا تحويل
A
إلى
B
(بمعنى آخر ، هناك دالة
(a: A) -> B
) ، ثم يمكننا كتابة دالة
(f: Functor<A>) -> Functor<B>
وقم بتسمية
map
:
interface Functor<A> { fun <B> map(f: (A) -> B): Functor<B> }
على عكس المشغّل نفسه ، لا يمكن أن تكون طريقة الخريطة وظيفة تعسفية:
- يجب أن تُرجع
map((a) -> a)
المُشغل نفسه
-
map((a) -> f(a)).map((b) -> g(b))
يجب أن تكون
map((a) -> f(a)).map((b) -> g(b))
مطابقة
map(a -> g(f(a))
على سبيل المثال ، ننفذ عامل توجيه يُرجع قيمة A تحتوي على عدد معين من وحدات البت العشوائية. لا يمكن استخدام واجهتنا في Kotlin بهذه السهولة (ولكن يمكنك ، إذا رغبت في ذلك) ، لذلك سنكتب طريقة تمديد:
أمثلة أخرى من functor مع
map
مفيدة
List<A>
غير قابلة للتغيير List<A>
MyInputStream<A>
Optional<A>
الآن يمكنك الذهاب إلى monads.
الموناد هو فاعل مع عمليتين إضافيتين. بادئ ذي بدء ، يحتوي monad ، على عكس المُشغل ، على عملية الإنشاء من ثابت ، وتسمى هذه العملية
lift
:
fun <A> lift(value: A): Monad<A> = TODO()
العملية الثانية تسمى
flatMap
، فهي أكثر تعقيدًا ، لذا سنعطي أولاً واجهة monad بالكامل:
interface Monad<A> {
الفرق الأكثر أهمية بين monad و functor هو أنه يمكن
دمج monad مع بعضها البعض ، وإنشاء monad جديدة والاستخلاص من كيفية تنفيذ monad - سواء كان يقرأ من القرص ، ما إذا كان يقبل معلمات إضافية لحساب قيمتها ، هل توجد هذه القيمة . النقطة المهمة الثانية - الأحاديات لا يتم دمجها بشكل متوازٍ ، ولكن بالتتابع ، مما يترك القدرة على إضافة منطق اعتمادًا على نتيجة الموناد الأول.
مثال:
ومع ذلك ، في هذا المثال لا يوجد ذكر لشبكة. بنفس القدر ، يمكن قراءة البيانات من ملف أو من قاعدة بيانات. يمكن قراءتها بشكل متزامن أو غير متزامن ، وهنا يمكن أن يكون هناك خطأ في التعامل - كل هذا يتوقف على التنفيذ المحدد للموناد ، الكود نفسه سيظل دون تغيير.
في البداية المثال أبسط ، الخيار أحادي. في kotlin ، ليست هناك حاجة حقيقية ، ولكن في Java / Scala ، إنها مفيدة للغاية:
data class Option<A>(val value: A?) { fun <B> map(f: (A) -> B): Option<B> = flatMap { a -> lift(f(a)) } fun <B> flatMap(f: (A) -> Option<B>): Option<B> = when(value) { null -> Option(null) else -> f(value) } } fun <A> lift(value: A?): Option<A> = Option(value) fun mul(a: Option<Int>, b: Option<Int>): Option<Int> = a.flatMap { a -> b.map { b -> a * b } } fun main(args: Array<String>) { println(mul(Option(4), Option(5)).value)
بصفتك monad of pozakovyristy ، دعنا نختتم العمل بقاعدة البيانات في monad:
data class DB<A>(val f: (Connection) -> A) { fun <B> map(f: (A) -> B): DB<B> = flatMap { a -> lift(f(a)) } fun <B> flatMap(f: (A) -> DB<B>): DB<B> = DB { conn -> f(this.f(conn)).f(conn) } } fun <A> lift(value: A): DB<A> = DB { value } fun select(id: Int): DB<String> = DB { conn -> val st = conn.createStatement()
هل ثقب الأرنب عميق؟
هناك مجموعة كبيرة ومتنوعة من الموناديات ، لكن الغرض الرئيسي منها هو استخلاص المنطق التجاري للتطبيق من بعض تفاصيل الحسابات التي تم إجراؤها:
- أن القيمة قد لا تكون موجودة:
data class Option<A>(value: A?)
- أن الحساب سيفشل:
data class Either<Error, A>(value: Pair<Error?, A?>)
- أن الحساب يمكن أن يكون كسولًا:
data class Defer<A>(value: () -> A)
- أو غير متزامن:
java.util.concurrent.CompletableFuture<A>
- أو لديك حالة وظيفية: حالة
data class State<S, A>(value: (S) -> Pair<S, A>)
قائمة الأسئلة التي لم تتم الإجابة عليها:
- الدوافع التطبيقية - رابط وسيط بين الدوافع والموناديات
- مجموعات مثل monads
- التراكيب من monads monotypic - gluesi السهم ، المحولات monadic
- تسلسل / اجتياز
- monads كما الآثار
- monads والتكرار ، كومة الفائض ، الترامبولين
- الترميز النهائي بدون علامات
- أنا أحادي
- وعموما حديقة الحيوان كاملة من monads القياسية
ما التالي؟
arrow-kt.iotypelevel.org/cats/typeclasses.htmlwiki.haskell.org/All_About_Monadsتجربتي عبارة عن تطبيق كامل النمط FP على Scala:
github.com/scf37/fpscala2سكرتير خاص أردت ملاحظة صغيرة ، اتضح كما هو الحال دائما.