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

9) بناة طريقة التمديد
لنبدأ ، ربما ، الأداة الأساسية - طرق التمديد من أي نوع التي تحول مثيل إلى خيار ، إما ، إلخ ، على وجه الخصوص:
.some من طريقة إنشاء none المطابق Option ..asRight ، .asLeft ؛.valid ، .invalid ، .validNel ، .invalidNel for Validated
اثنين من المزايا الرئيسية لاستخدامها:
- إنه أكثر إحكاما وفهمًا (حيث يتم حفظ تسلسل استدعاءات الطريقة).
- على عكس خيارات المُنشئ ، يتم تمديد أنواع الإرجاع من هذه الأساليب لتشمل نوعًا فائقًا ، أي:
import cats.implicits._ Some("a")
على الرغم من تحسّن الاستدلال على الكتابة على مر السنين ، كما انخفض عدد المواقف المحتملة التي يساعد فيها هذا السلوك المبرمج على التزام الهدوء ، إلا أن أخطاء الترجمة بسبب الكتابة التخصصية المفرطة لا تزال ممكنة في Scala اليوم. في كثير من الأحيان ، تظهر الرغبة في إثارة رأسك على الطاولة عند العمل مع
Either (انظر
Scala with Cats الفصل 4.4.2).
شيء واحد آخر حول الموضوع: لا يزال لدى
.asRight و
.asLeft معلمة نوع أخرى. على سبيل المثال ،
"1".asRight[Int] هو
Either[Int, String] . إذا لم يتم توفير هذه المعلمة ، سيحاول المترجم إخراجها والحصول على
Nothing . ومع ذلك ، فهي أكثر ملاءمة من توفير كلتا المعلمتين في كل مرة أو عدم تقديمهما ، كما في حالة المُنشئات.
8) خمسون ظلال *>
المشغل *> المحدد في أي طريقة
Apply (أي ، في
Applicative ،
Monad ، إلخ) يعني ببساطة "معالجة الحساب الأولي واستبدال النتيجة بما هو محدد في الوسيطة الثانية". بلغة الكود (في حالة
Monad ):
fa.flatMap(_ => fb)
لماذا استخدام عامل رمزي غامض لعملية لا يكون لها تأثير ملحوظ؟ عند بدء استخدام ApplicativeError و / أو MonadError ، ستجد أن العملية تحتفظ بتأثير الخطأ لسير العمل بأكمله. خذ
Either كمثال:
import cats.implicits._ val success1 = "a".asRight[Int] val success2 = "b".asRight[Int] val failure = 400.asLeft[String] success1 *> success2
كما ترون ، حتى في حالة حدوث خطأ ، يظل الحساب قصير الدائرة. *> سوف تساعدك في العمل مع الحسابات المؤجلة في
Monix و
IO وما شابه.
هناك عملية متناظرة ، <*. لذلك ، في حالة المثال السابق:
success1 <* success2
أخيرًا ، إذا كان استخدام الرموز غريبًا عليك ، فليس من الضروري اللجوء إليها. *> مجرد اسم مستعار لـ
productR ، و * <اسم مستعار لـ
productL .
تعليق
في محادثة شخصية ، لاحظ آدم وارسكي (شكراً يا آدم!) بحق أنه بالإضافة إلى *> (
productR ) هناك أيضًا >> من
FlatMapSyntax . يتم تعريف >> بنفس طريقة
fa.flatMap(_ => fb) ، ولكن مع
fa.flatMap(_ => fb) :
- يتم تعريفه بشكل مستقل عن
productR ، وبالتالي ، إذا تغير عقد هذه الطريقة لسبب ما (نظريًا ، يمكن تغييره دون انتهاك قوانين monadic ، لكنني لست متأكدًا من MonadError ) ، فلن تعاني ؛ - الأهم من ذلك ، >> لديها المعامل الثاني الذي يطلق عليه الاتصال بالاسم ، أي
fb: => F[B] . يصبح الاختلاف في الدلالات أمرًا أساسيًا إذا أجريت حسابات يمكن أن تؤدي إلى انفجار مكدس.
بناءً على ذلك ، بدأت في استخدام *> أكثر من مرة. بطريقة أو بأخرى ، لا تنس العوامل المذكورة أعلاه.
7) رفع الأشرعة!
كثير من الوقت لوضع مفهوم
lift في رؤوسهم. ولكن عندما تنجح ، ستجد أنه في كل مكان.
مثل العديد من المصطلحات ارتفاع في البرمجة الوظيفية ، جاء
lift من
نظرية الفئة . سأحاول شرح: إجراء عملية ، تغيير التوقيع من نوعه بحيث يصبح مرتبطًا مباشرةً بنوع الملخص واو.
في Cats ، أبسط مثال هو
Functor :
def lift[A, B](f: A => B): F[A] => F[B] = map(_)(f)
هذا يعني: تغيير هذه الوظيفة بحيث تعمل على نوع معين من functor F.
غالبًا ما تكون وظيفة الرفع مرادفة للمنشآت المتداخلة لنوع معين. لذلك ،
EitherT.liftF هو
EitherT.right. الأساس
EitherT.right. مثال من Scaladoc :
import cats.data.EitherT import cats.implicits._ EitherT.liftF("a".some)
الكرز على الكعكة: يوجد
lift في كل مكان في مكتبة Scala القياسية. المثال الأكثر شعبية (وربما الأكثر فائدة في العمل اليومي) هو
PartialFunction :
val intMatcher: PartialFunction[Int, String] = { case 1 => "jak się masz!" } val liftedIntMatcher: Int => Option[String] = intMatcher.lift liftedIntMatcher(1)
الآن يمكننا الانتقال إلى المزيد من القضايا الملحة.
6) mapN
mapN هي وظيفة مساعدة مفيدة للعمل مع tuples. مرة أخرى ، هذه ليست حداثة ، ولكنها بديل للمشغل القديم الجيد
|@| إنه صراخ.
إليك ما تشبهه mapN في حالة وجود عنصرين:
في جوهرها ، يسمح لنا بتعيين قيم داخل tuple من أي F تمثل مجموعة شبه (منتج) وعامل توجيه (خريطة). لذلك:
import cats.implicits._ ("a".some, "b".some).mapN(_ ++ _)
بالمناسبة ، لا تنس أنه مع القطط تحصل على خريطة وخريطة
leftmap :
("a".some, List("b","c").mapN(_ ++ _))
وظيفة
.mapN مفيدة أخرى هي إنشاء مثيل لحالة الحالات:
case class Mead(name: String, honeyRatio: Double, agingYears: Double) ("półtorak".some, 0.5.some, 3d.some).mapN(Mead) //Some(Mead(półtorak,0.5,3.0))
بالطبع ، يمكنك استخدام عامل التشغيل حلقة لهذا ، لكن mapN يتجنب المحولات أحادية اللون في الحالات البسيطة.
import cats.effect.IO import cats.implicits._
الطرق لها نتائج مماثلة ، لكن الأخير يستغني عن المحولات الأحادية.
5) متداخل
Nested هي في الأساس مضاعفة معممة من المحولات الأحادية. كما يوحي الاسم ، فإنه يسمح لك بإجراء عمليات المرفقات في ظل ظروف معينة. هنا مثال على
.map(_.map( : import cats.implicits._ import cats.data.Nested val someValue: Option[Either[Int, String]] = "a".asRight.some Nested(someValue).map(_ * 3).value
بالإضافة إلى
Functor ، يعمم
Nested Applicative و
ApplicativeError و
Traverse . معلومات إضافية وأمثلة
هنا .
4) .Recover / .recoverWith / .handleError / .handleErrorWith / .valueOr
البرمجة الوظيفية في Scala لها علاقة كبيرة بمعالجة تأثير الخطأ.
ApplicativeError و
MonadError بعض الطرق المفيدة ، وقد يكون من المفيد لك معرفة الفروق الدقيقة بين الأربعة الرئيسية. لذلك ، مع
ApplicativeError F[A]:handleError يحول كل الأخطاء في نقطة الاتصال إلى A وفقًا للوظيفة المحددة.recover الأعمال بطريقة مماثلة ، ولكنها تقبل الوظائف الجزئية ، وبالتالي يمكنها تحويل الأخطاء التي حددتها إلى A.- يشبه
handleError ، لكن يجب أن تبدو نتيجته مثل F[A] ، مما يعني أنه يساعدك في تحويل الأخطاء. recoverWith أعمال مثل recoverWith ، ولكنها تتطلب أيضًا F[A] كنتيجة.
كما ترون ، يمكنك تقييد
handleErrorWith recoverWith ، والتي تغطي جميع الوظائف الممكنة. ومع ذلك ، فإن كل طريقة لها مزاياها وهي مريحة بطريقتها الخاصة.
بشكل عام ، أنصحك أن تتعرف على واجهة برمجة
التطبيقات ApplicativeError ، والتي تعد واحدة من أغنى البرامج في القطط ورثت في MonadError - مما يعني أنه مدعوم في
cats.effect.IO ،
monix.Task ، إلخ.
هناك طريقة أخرى لكل من
Either/EitherT و
Validated و
.valueOr -
.valueOr . بشكل أساسي ، تعمل مثل
.getOrElse for
Option ، ولكنها عامة للفئات التي تحتوي على شيء "إلى اليسار".
import cats.implicits._ val failure = 400.asLeft[String] failure.valueOr(code => s"Got error code $code")
3) القطط الزقاق
زقاق القطط هو حل مناسب لحالتين:
- حالات الطبقات البلاط التي لا تتبع قوانينها 100 ٪ ؛
- typklassy مساعدة غير عادية ، والتي يمكن استخدامها بشكل صحيح.
من الناحية التاريخية ، يعد المثال الأحادي لـ
Try الأكثر شعبية في هذا المشروع ، لأن
Try ، كما تعلم ، لا يلبي جميع القوانين الأحادية من حيث الأخطاء القاتلة. الآن هو حقا قدم للقطط.
على الرغم من ذلك ، أوصي بأن تتعرف على
هذه الوحدة ، فقد تبدو مفيدة لك.
2) علاج مسؤولة الواردات
يجب أن تعلم - من الوثائق أو الكتاب أو من مكان آخر - أن القطط تستخدم تسلسل هرمي محدد للاستيراد:
cats.x الأساسية (النواة) ؛
cats.data لأنواع البيانات مثل محولات Validated أو monad وغيرها ؛
cats.syntax.x._ لدعم أساليب الامتداد بحيث يمكنك استدعاء sth.asRight ، sth.pure ، وما إلى ذلك ؛
cats.instances.x. _ لاستيراد تنفيذ أنواع المطبوعات المختلفة مباشرة في النطاق الضمني لأنواع معينة ، بحيث لا يحدث خطأ "ضمني غير موجود" عند الاتصال ، على سبيل المثال ، sth.pure.
بالطبع ، لاحظت استيراد
cats.implicits._ ، الذي يستورد كل بناء الجملة وجميع مثيلات فئة الكتابة في النطاق الضمني.
من حيث المبدأ ، عند التطوير باستخدام Cats ، يجب أن تبدأ بتسلسل معين من الواردات من الأسئلة الشائعة ، وهي:
import cats._ import cats.data._ import cats.implicits._
إذا تعرفت على المكتبة بشكل أفضل ، يمكنك دمجها حسب ذوقك. اتبع قاعدة بسيطة:
- يوفر
cats.syntax.x بناء جملة ملحق يتعلق x؛ - يوفر
cats.instances.x فئات مثيل.
على سبيل المثال ، إذا كنت بحاجة إلى
.asRight ، وهي طريقة امتداد لـ
Either ،
.asRight بما يلي:
import cats.syntax.either._ "a".asRight[Int]
من ناحية أخرى ، للحصول على
Option.pure عليك استيراد
cats.syntax.monad AND cats.instances.option :
import cats.syntax.applicative._ import cats.instances.option._ "a".pure[Option]
عن طريق تحسين عملية الاستيراد يدويًا ، ستحد من النطاقات الضمنية في ملفات Scala الخاصة بك وبالتالي تقلل من وقت الترجمة.
ومع ذلك ، يرجى: عدم القيام بذلك إذا لم يتم استيفاء الشروط التالية:
- لقد أتقنت بالفعل القطط بشكل جيد
- يمتلك فريقك المكتبة في نفس المستوى
لماذا؟ للأسباب التالية:
وذلك لأن كل من
cats.instances.OptionInstances .
cats.instances.OptionInstances .
cats.instances.OptionInstances .
cats.instances.OptionInstances هي امتدادات
cats.instances.OptionInstances .
cats.instances.OptionInstances .
cats.instances.OptionInstances . في الواقع ، نحن نستورد نطاقه الضمني مرتين ، أكثر مما نخلط بين المترجم.
علاوة على ذلك ، لا يوجد سحر في التسلسل الهرمي للمتورطين - هذا تسلسل واضح لملحقات النوع. تحتاج فقط إلى الرجوع إلى تعريف
cats.implicits وفحص التسلسل الهرمي للنوع.
لنحو 10 إلى 20 دقيقة يمكنك دراستها بما يكفي لتجنب مثل هذه المشاكل - صدقوني ، هذا الاستثمار سيؤتي ثماره بالتأكيد.
1) لا تنسى تحديثات القطط!
قد تعتقد أن مكتبة FP الخاصة بك غير صالحة
scalaz ، ولكن في الواقع ،
scalaz تحديث
cats scalaz بنشاط. خذ القطط كمثال. إليك فقط أحدث التغييرات:
لذلك ، عند العمل مع المشاريع ، لا تنس التحقق من إصدار المكتبة ، وقراءة الملاحظات للإصدارات الجديدة والتحديث في الوقت المناسب.