غير علمي عن الموناديات

مرحبا بالجميع.

بعد أربع سنوات من البرمجة في 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 بهذه السهولة (ولكن يمكنك ، إذا رغبت في ذلك) ، لذلك سنكتب طريقة تمديد:

 //  - ,     ,   map data class MyRandom<A>( val get: (bits: Int) -> A ) { companion object { val intRandom: MyRandom<Int> = MyRandom { Random.nextBits(it) } val hexRandom: MyRandom<String> = intRandom.map { it.toString(16) } } } //  map   fun <A, B> MyRandom<A>.map(f: (A) -> B): MyRandom<B> = MyRandom(get = {bits -> f(get(bits)) }) fun main(args: Array<String>) { println("random=" + MyRandom.intRandom.get(12)) //  random=1247 println("hexRandom=" + MyRandom.hexRandom.get(12)) //  hexRandom=c25 } 

أمثلة أخرى من 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> { //   ,  map     - //    flatMap  lift fun <B> map(f: (A) -> B): Monad<B> = flatMap { a -> lift(f(a)) } fun <B> flatMap(f: (A) -> Monad<B>): Monad<B> } fun <A> lift(value: A): Monad<A> = TODO() 

الفرق الأكثر أهمية بين monad و functor هو أنه يمكن دمج monad مع بعضها البعض ، وإنشاء monad جديدة والاستخلاص من كيفية تنفيذ monad - سواء كان يقرأ من القرص ، ما إذا كان يقبل معلمات إضافية لحساب قيمتها ، هل توجد هذه القيمة . النقطة المهمة الثانية - الأحاديات لا يتم دمجها بشكل متوازٍ ، ولكن بالتتابع ، مما يترك القدرة على إضافة منطق اعتمادًا على نتيجة الموناد الأول.

مثال:

 // ,     Int //       -      //           val readInt: Monad<Int> = TODO() // ,      -  fun readBytes(len: Int): Monad<ByteArray> = TODO() // ,     ,    val bytes: Monad<ByteArray> = readInt.flatMap {len -> if (len > 0) readBytes(len) //    -   else lift(ByteArray(0)) //  ,    } 

ومع ذلك ، في هذا المثال لا يوجد ذكر لشبكة. بنفس القدر ، يمكن قراءة البيانات من ملف أو من قاعدة بيانات. يمكن قراءتها بشكل متزامن أو غير متزامن ، وهنا يمكن أن يكون هناك خطأ في التعامل - كل هذا يتوقف على التنفيذ المحدد للموناد ، الكود نفسه سيظل دون تغيير.

في البداية المثال أبسط ، الخيار أحادي. في 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) // 20 println(mul(Option(null), Option(5)).value) // null println(mul(Option(4), Option(null)).value) // null println(mul(Option(null), Option(null)).value) // null } 

بصفتك 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() // .... TODO() } fun update(value: String): DB<Unit> = DB { conn -> val st = conn.createStatement() // .... TODO() } fun selectThenUpdate(id: Int): DB<Unit> = select(id).flatMap { value -> update(value) } fun executeTransaction(c: Connection): Unit { //  ,     //          val body: DB<Unit> = selectThenUpdate(42) //  ,   select  update body.f(c) c.commit() } 

هل ثقب الأرنب عميق؟


هناك مجموعة كبيرة ومتنوعة من الموناديات ، لكن الغرض الرئيسي منها هو استخلاص المنطق التجاري للتطبيق من بعض تفاصيل الحسابات التي تم إجراؤها:

  • أن القيمة قد لا تكون موجودة: 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.io
typelevel.org/cats/typeclasses.html
wiki.haskell.org/All_About_Monads

تجربتي عبارة عن تطبيق كامل النمط FP على Scala:
github.com/scf37/fpscala2

سكرتير خاص أردت ملاحظة صغيرة ، اتضح كما هو الحال دائما.

Source: https://habr.com/ru/post/ar454534/


All Articles