مواء! ابدأ في استخدام القطط في مشروعك الآن

مقدمة لطيفة لمكتبة القطط.

المقدمة

القطط هي مكتبة توفر التجريدية للبرمجة الوظيفية في سكالا.

هناك بعض المنشورات والدورات التدريبية الرائعة على Cats الموجودة على الويب (مثل Herding cats وبرنامج تعليمي حول Scala Exercises) ، لكنها تميل إلى استكشاف فئات الفئات / الأنواع المطبقة في المكتبة بدلاً من إعطاء استعداد عملي استخدام أمثلة لكيفية استخدام القطط في الكودبات ​​الموجودة. بالكاد يقوم منشور المدونة هذا بخدش سطح ما يمكن أن تفعله Cats ، ولكن بدلاً من ذلك يوفر مقدمة موجزة عن الأنماط التي من المرجح أن تستفيد منها في مشروع Scala الخاص بك. إذا كنت تستخدم أي monad مثل Future أو Option على أساس يومي ، فمن المحتمل جدًا أن تتمكن Cats من تبسيط وتحسين إمكانية قراءة التعليمات البرمجية الخاصة بك.

يرجى الرجوع إلى Wats Cats على GitHub للحصول على إرشادات حول كيفية إضافة المكتبة إلى تبعيات مشروعك. نحن نتمسك بالإصدار 0.9.0 في المنشور بأكمله.

دعنا ننتقل من خلال حزمة المكتبة ، وننظر إلى بناء الجملة المتاح في كل حزمة.

المساعدون للخيار وإما

استيراد القطط.

يتيح استيراد هذه الحزمة بناء جملة obj.some - مكافئًا لبعض (obj). الفرق الحقيقي الوحيد هو أن القيمة قد تم نشرها بالفعل على الخيار [T] من Some [T].

يمكن أن يؤدي استخدام obj.some بدلاً من Some (obj) أحيانًا إلى تحسين قابلية اختبارات الوحدة. على سبيل المثال ، إذا قمت بإضافة الفئة الضمنية التالية إلى BaseSpec أو TestHelper أو أيًا كانت تسمى الفئة الأساسية للاختبارات:

ثم يمكنك استخدام بناء الجملة المتسلسل الموضح أدناه (على افتراض أن اختبارات الوحدة الخاصة بك مبنية على scalamock ؛ انظر أيضًا منشورًا لـ Bartosz Kowalik):

هذا أكثر قابلية للقراءة من Future.successful (بعض (مستخدم)) ، خاصة إذا كان هذا النمط يتكرر بشكل متكرر في مجموعة الاختبار. يساعد تسلسل .some.asFuture في النهاية بدلاً من وضعها في المقدمة أيضًا في التركيز على ما يتم إرجاعه فعليًا وليس على نوع المجمع المتوقع.

لا شيء [T] ، بدوره ، هو اختصار لـ Option.empty [T] وهو بلا شيء فقط ، لكنه بالفعل منبثق من None.typeto Option [T]. توفير نوع أكثر تخصصًا يساعد أحيانًا برنامج التحويل البرمجي Scala على استنتاج نوع التعبيرات التي تحتوي على بلا.

استيراد cats.syntax.either._

obj.asRight هو Right (obj) ، obj.asLeft هو Left (obj). في كلتا الحالتين يتم توسيع نوع القيمة التي تم إرجاعها من اليمين أو اليسار إلى أي منهما. كما كان الحال مع بعض الشيء ، فإن هؤلاء المساعدين سهلون للاندماج مع. asFuture لتحسين قابلية اختبارات الوحدة:

Either.fromOption (الخيار: الخيار [A] ، ifNone: => E) ، بدوره ، هو مساعد مفيد لتحويل خيار إلى إما. إذا كان الخيار الموفر هو بعض (x) ، يصبح اليمين (x). خلاف ذلك يصبح Leftwith المقدمة ifNone القيمة في الداخل.

مثيلات الحزم وبناء الجملة الديكارتي

استيراد القطط. ._

هناك نوعان من فئات الأنواع التي تعد أساسية بالنسبة للقطط (وبشكل عام في البرمجة الوظيفية القائمة على الفئة) ، وأهمها هي Functor و Applicative و Monad. نحن لا نذهب إلى مزيد من التفاصيل في منشور المدونة هذا (انظر على سبيل المثال ، البرنامج التعليمي الذي سبق ذكره) ، ولكن ما هو مهم أن نعرف أنه لاستخدام معظم صيغة Cats ، فإنك تحتاج أيضًا إلى استيراد مثيلات فئة النوع الضمني للهياكل التي تستخدمها. إعادة التشغيل مع.

عادة ، يكفي استيراد حزمة القطط المناسبة. على سبيل المثال ، عند إجراء التحويلات على العقود المستقبلية ، ستحتاج إلى استيراد القطط. المواد. المستقبل. تسمى الحزم المقابلة للخيارات والقوائم cats.instances.option._ و cats.inaterial.list._. أنها توفر مثيلات فئة الضمنية التي يحتاج بناء جملة Cats للعمل بشكل صحيح.

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

استيراد cats.syntax.cartesian._

توفر الحزمة الديكارتية | @ | بناء جملة ، والذي يسمح بإنشاء سهل الاستخدام لتطبيق دالة تأخذ أكثر من معلمة واحدة على قيم فعالة متعددة (مثل العقود المستقبلية).

دعنا نقول أن لدينا 3 عقود مستقبلية ، واحدة من النوع Int ، واحدة من النوع String ، واحدة من النوع User وطريقة قبول ثلاث معلمات - Int ، String و User.

هدفنا هو تطبيق الوظيفة على القيم المحسوبة بواسطة هذه العقود الآجلة الثلاثة. مع بناء جملة ديكارت يصبح هذا سهلاً وموجزًا ​​للغاية:

كما أشرنا من قبل ، لتوفير المثيل الضمني (أي الديكارتية [المستقبل]) المطلوبة ل | @ | للعمل بشكل صحيح ، يجب عليك استيراد القطط. المواد. المستقبل.

يمكن التعبير عن هذه الفكرة أعلاه بشكل أقصر ، فقط:

ستكون نتيجة التعبير أعلاه من النوع Future [ProcessingResult]. إذا فشل أي من العقود الآجلة المتسلسلة ، فسوف يفشل المستقبل الناتج أيضًا مع استثناء نفس المستقبل الأول في السلسلة (هذا هو سلوك الفشل). الأهم من ذلك ، سيتم تشغيل جميع العقود المستقبلية بشكل متوازٍ ، على عكس ما سيحدث في الفهم:

في المقتطف أعلاه (والذي يتم تحويله تحت الغطاء إلى flatMap ومكالمات الخريطة) ، لن يتم تشغيل stringFuture حتى تكتمل intFuture بنجاح ، وبنفس الطريقة سيتم تشغيل userFuture فقط بعد اكتمال stringFuture. ولكن نظرًا لأن الحسابات مستقلة عن بعضها البعض ، فمن العملي تمامًا تشغيلها بالتوازي مع | @ | في حين أن.

عبور

استيراد cats.syntax.traverse._

قطع

إذا كان لديك نسخة من النوع F [A] والتي يمكن تعيينها (مثل Future) وممتعة من النوع A => G [B] ، فإن الاتصال بـ obj.map (متعة) يمنحك F [G [ ب]]. في العديد من الحالات الواقعية الشائعة ، مثل عندما يكون F هو Option و G هو Future ، ستحصل على Option [Future [B]] ، وهو على الأرجح ليس ما تريد.

يأتي اجتياز كحل هنا. إذا اتصلت بالعبور بدلاً من الخريطة ، مثل obj.traverse (مرح) ، فستحصل على G [F [A]] ، والذي سيكون Future [خيار [B]] في حالتنا ؛ هذا مفيد أكثر وأسهل في المعالجة من الخيار [المستقبل [B]].

كملاحظة جانبية ، هناك أيضًا طريقة مخصصة Future.traverse في كائن رفيق Future ، ولكن إصدار Cats أكثر قابلية للقراءة ويمكن أن يعمل بسهولة على أي بنية تتوفر بها فئات أنواع معينة.

تسلسل

يمثل التسلسل مفهومًا أكثر بساطة: يمكن اعتباره مجرد تبديل الأنواع من F [G [A]] إلى G [F [A]] دون حتى تعيين القيمة المغلقة كما يفعل traverse.

obj.sequence يتم تنفيذه في الواقع في Cats كـ obj.traverse (هوية). من ناحية أخرى ، فإن obj.traverse (مرح) يكافئ تقريبًا obj.map (مرح).

flatTraverse

إذا كان لديك obj من النوع F [A] وممتعة للدالة من النوع A => G [F [B]] ، فعمل obj.map (f) ينتج عنه النوع F [G [F [B]]] - من غير المرجح أن تكون ما تريد.

يساعد اجتياز obj بدلاً من التعيين قليلاً - ستحصل على G [F [F [B]] بدلاً من ذلك. نظرًا لأن G عادة ما تكون مثل Future و F هي قائمة أو خيار ، فسينتهي بك الأمر إلى [Future [Option [A]] أو Future [List [List [A]]] - بعض الشيء محرج في المعالجة.

يمكن أن يكون الحل هو تعيين النتيجة باستدعاء _.flatten مثل:

وبهذه الطريقة ستحصل على النوع المطلوب G [F [B]] في النهاية.

ومع ذلك ، هناك اختصار أنيق لهذا يسمى flatTraverse:

وهذا يحل مشكلتنا للأبد.

محولات مناد

استيراد cats.data.OptionT

يمكن اعتبار مثيل OptionT [F، A] بمثابة التفاف على F [Option [A]] الذي يضيف عدة طرق مفيدة خاصة بالأنواع المتداخلة التي لا تتوفر في F أو Option نفسها. غالبًا ما يكون F الخاص بك هو Future (أو أحيانًا DBIO الخاص بـ slick ، ​​ولكن هذا يتطلب تنفيذ فئات من نوع Cats مثل Functor أو Monad for DBIO). الأغلفة مثل OptionT تُعرف عمومًا باسم المحولات الأحادية.

هناك نمط شائع تمامًا يتمثل في تعيين القيمة الداخلية المخزنة داخل مثيل F [Option [A]] لمثيل F [Option [B]] مع دالة من النوع A => B. يمكن القيام بذلك باستخدام بناء الجملة المطوّلة إلى حد ما مثل:

مع استخدام OptionT ، يمكن تبسيط ذلك على النحو التالي:

ستظهر الخريطة أعلاه قيمة النوع OptionT [Future، String].

للحصول على القيمة المستقبلية [Option [String]] الأساسية ، ما عليك سوى استدعاء .value على مثيل OptionT. كما يعد حلاً قابلاً للتطبيق للتبديل الكامل إلى OptionT [Future، A] في أنواع المعلمات / الإرجاع للطريقة والتخلص بالكامل (أو بالكامل تقريبًا) Future [Option [A]] في تعريفات النوع.

هناك عدة طرق لإنشاء مثيل OptionT. يتم تبسيط رؤوس الطريقة في الجدول أدناه بشكل بسيط: يتم تخطي معلمات الكتابة وفئات الكتابة التي تتطلبها كل طريقة.

في كود الإنتاج ، ستستخدم أكثر شيوع بناء جملة OptionT (...) من أجل التفاف مثيل من Future [Option [A]] في Option [F، A]. الطرق الأخرى ، بدورها ، أثبتت فائدتها لإعداد قيم وهمية مكتوبة على OptionT في اختبارات الوحدة.

لقد صادفنا بالفعل إحدى طرق OptionT‘ ، وهي الخريطة. هناك العديد من الطرق الأخرى المتاحة والتي تختلف في الغالب حسب توقيع الوظيفة التي يقبلونها كمعلمة. كما كان الحال مع الجدول السابق ، يتم تخطي فئات الكتابة المتوقعة.

في الممارسة العملية ، من الأرجح أنك تستخدم الخريطة و semiflatMap.

كما هو الحال دائمًا مع flatMap و map ، يمكنك استخدامه ليس فقط بشكل صريح ، ولكن أيضًا تحت غطاء محرك السيارة في الفهم ، كما في المثال أدناه:

سيقوم مثيل OptionT [Future، Money] الذي تم إرجاعه بواسطة getReservedFundsForUser بإحاطة Nonevalue إذا كان أي من الطرق المكونة الثلاثة بإرجاع OptionT المطابق بلا. خلاف ذلك ، إذا كانت نتيجة المكالمات الثلاثة تحتوي على بعض ، فإن النتيجة النهائية سوف تحتوي أيضًا على بعض.

استيراد القطط

يعد إما إما [F ، A ، B] محول أحادي لـ "إما" - يمكنك التفكير في الأمر على أنه التفاف على قيمة F [Either [A، B]].

تمامًا كما في القسم أعلاه ، قمت بتبسيط رؤوس الطريقة وتخطي معلمات النوع أو حدود السياق والحدود السفلية.

دعنا نلقي نظرة سريعة على كيفية إنشاء مثيل EitherT:

فقط للتوضيح: يلف EitherT.fromEither المتوفر إما في F ، في حين يلف EitherT.right و EitherT.left القيمة داخل F المتوفر واليسار ، على التوالي. إما أن يلف .Pure ، بدوره ، القيمة B المقدمة إلى اليمين ثم إلى F.

هناك طريقة أخرى مفيدة لإنشاء مثيل EitherT وهي استخدام طرق OptionT to إلى اليسار وإلى اليمين:

toRight يشبه إلى حد كبير الطريقة التي ذكرها من قبل: إما من أوبشن تم بناء إما من خيار ، يخلق toRight EitherT من OptionT. إذا كان OptionTstores الأصلي بعض القيمة ، سيتم لفه إلى اليمين ؛ وإلا فإن القيمة المقدمة باعتبارها المعلمة اليسرى سيتم لفها إلى اليسار.

toLeft هو نظير toRight الذي يلتف قيمة بعض إلى يسار ويحول لا شيء إلى يسار لإغلاق القيمة الصحيحة المقدمة. يستخدم هذا بشكل أقل شيوعًا في الممارسة العملية ، ولكن يمكن تقديمه على سبيل المثال لفرض الشيكات التفرد في التعليمات البرمجية. نرجع إلى اليسار إذا تم العثور على القيمة ، واليمين إذا لم يكن موجودًا في النظام بعد.

تتشابه الطرق المتوفرة في EitherT مع تلك التي رأيناها في OptionT ، ولكن هناك بعض الاختلافات الملحوظة. قد تتعرض لبعض الالتباس في البداية عندما يتعلق الأمر على سبيل المثال خريطة. في حالة OptionT ، كان من الواضح تمامًا ما يجب القيام به: يجب أن تتخطى الخريطة الخيار الموجود داخل Future ، ثم تقوم بتعيين الخريطة المغلقة نفسها. هذا أقل وضوحًا قليلاً في حالة EitherT: هل يجب تعيينه على كل من قيم Leftand Right أو القيمة الصحيحة فقط؟

الإجابة هي أن EitherT منحازة إلى اليمين ، وبالتالي فإن الخريطة البسيطة تتعامل فعليًا مع القيمة الصحيحة. هذا على عكس إما في مكتبة Scala القياسية حتى 2.11 ، والتي بدورها غير منحازة: لا توجد خريطة متوفرة في إما ، فقط لإسقاطاتها اليمنى واليسرى.

بعد قولي هذا ، دعنا نلقي نظرة سريعة على الأساليب المنحرفة الصحيحة التي يوفرها EitherT [F ، A ، B]:

كملاحظة جانبية ، هناك أيضًا طرق معينة في EitherT (من المحتمل أن تحتاجها في مرحلة ما) والتي يتم تعيينها على القيمة اليسرى ، مثل leftMap ، أو على القيمتين اليسرى واليمنى ، مثل الطي أو bimap.

إما أن يكون مفيدًا جدًا لعمليات التحقق المتسلسلة بسرعة تفشل:

في المثال أعلاه ، نجري عمليات فحص متعددة مقابل العنصر واحدًا تلو الآخر. في حالة فشل أي من الاختبارات ، ستحتوي EitherT الناتجة على قيمة يسرى. خلاف ذلك ، إذا كانت جميع عمليات التحقق تعطي حقًا (بالطبع نعني حقًا ملفوفًا في EitherT) ، فإن النتيجة النهائية ستحتوي أيضًا على اليمين. هذا سلوك فاشل: نحن نوقف بشكل فعال تدفق الفهم في أول نتيجة يسارية.

إذا كنت تبحث بدلاً من ذلك عن التحقق من صحة الأخطاء التي تتراكم (على سبيل المثال عند التعامل مع بيانات النموذج المقدمة من قبل المستخدم) ، فقد يكون cats.data.Validated خيارًا جيدًا.

مشاكل شائعة

إذا لم يتم تجميع أي شيء كما هو متوقع ، فتأكد أولاً من أن جميع المتورطين في القطط المطلوبة في النطاق - فقط حاول استيراد cats.implicits._ وانظر إذا استمرت المشكلة. كما ذكرنا من قبل ، من الأفضل استخدام عمليات الاستيراد الضيقة ، ولكن إذا لم يتم تجميع الشفرة ، فمن المفيد في بعض الأحيان استيراد المكتبة بأكملها للتحقق مما إذا كانت ستحل المشكلة.

إذا كنت تستخدم العقود المستقبلية ، فتأكد من توفير ExecutionContext الضمني في النطاق ، وإلا فلن تكون القطط قادرة على استنتاج المثيلات الضمنية لفئات أنواع Future.

قد يكون المترجم في كثير من الأحيان مشاكل مع استنباط معلمات النوع للالعكس و sequencemethods. الحل الواضح هو تحديد هذه الأنواع مباشرة ، مثل list.traverse [Future، Unit] (fun). قد يصبح هذا مطولًا تمامًا في حالات معينة ، والطريقة الأفضل هي تجربة الأساليب المكافئة traverseU و sequenceU ، مثل list.traverseU (متعة). يفعلون بعض الخدع على مستوى النوع (مع cats.Unapply ، وبالتالي فإن U) لمساعدة المترجم في استنتاج معلمات النوع.

تقارير IntelliJ في بعض الأحيان أخطاء في التعليمات البرمجية المحملة Cats على الرغم من أن المصدر يمر تحت scalac. أحد الأمثلة على ذلك هو استدعاء أساليب cats.data.Nested class ، والتي يتم تجميعها بشكل صحيح تحت scalac ، ولكن لا تحقق من النوع ضمن برنامج مترجم العرض التقديمي IntelliJ. يجب أن تعمل دون مشكلة تحت IDE Scala.

كنصيحة للتعلم المستقبلي: يصعب فهم فئة التطبيقية ، على الرغم من أهميتها الأساسية في البرمجة الوظيفية. في رأيي ، الأمر أقل سهولة من Functor أو Monad ، على الرغم من أنه يقف في الحقيقة بين Functor و Monad في التسلسل الهرمي للميراث. أفضل طريقة لفهم التطبيقية هي أن نفهم أولاً كيف يعمل المنتج (الذي يحول F [A] و F [B] إلى F [(A ، B)]) بدلاً من التركيز على عملية ap الغريبة بحد ذاتها.