9 نصائح لاستخدام مكتبة Cats في Scala

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

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



9) بناة طريقة التمديد


لنبدأ ، ربما ، الأداة الأساسية - طرق التمديد من أي نوع التي تحول مثيل إلى خيار ، إما ، إلخ ، على وجه الخصوص:

  • .some من طريقة إنشاء none المطابق Option .
  • .asRight ، .asLeft ؛
  • .valid ، .invalid ، .validNel ، .invalidNel for Validated

اثنين من المزايا الرئيسية لاستخدامها:

  1. إنه أكثر إحكاما وفهمًا (حيث يتم حفظ تسلسل استدعاءات الطريقة).
  2. على عكس خيارات المُنشئ ، يتم تمديد أنواع الإرجاع من هذه الأساليب لتشمل نوعًا فائقًا ، أي:

 import cats.implicits._ Some("a") //Some[String] "a".some //Option[String] 

على الرغم من تحسّن الاستدلال على الكتابة على مر السنين ، كما انخفض عدد المواقف المحتملة التي يساعد فيها هذا السلوك المبرمج على التزام الهدوء ، إلا أن أخطاء الترجمة بسبب الكتابة التخصصية المفرطة لا تزال ممكنة في 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 //Right(b) success2 *> success1 //Right(a) success1 *> failure //Left(400) failure *> success1 //Left(400) 

كما ترون ، حتى في حالة حدوث خطأ ، يظل الحساب قصير الدائرة. *> سوف تساعدك في العمل مع الحسابات المؤجلة في Monix و IO وما شابه.

هناك عملية متناظرة ، <*. لذلك ، في حالة المثال السابق:

 success1 <* success2 //Right(a) 

أخيرًا ، إذا كان استخدام الرموز غريبًا عليك ، فليس من الضروري اللجوء إليها. *> مجرد اسم مستعار لـ 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) //EitherT(Some(Right(a))) EitherT.liftF(none[String]) //EitherT(None) 

الكرز على الكعكة: يوجد lift في كل مكان في مكتبة Scala القياسية. المثال الأكثر شعبية (وربما الأكثر فائدة في العمل اليومي) هو PartialFunction :

 val intMatcher: PartialFunction[Int, String] = { case 1 => "jak się masz!" } val liftedIntMatcher: Int => Option[String] = intMatcher.lift liftedIntMatcher(1) //Some(jak się masz!) liftedIntMatcher(0) //None intMatcher(1) //jak się masz! intMatcher(0) //Exception in thread "main" scala.MatchError: 0 

الآن يمكننا الانتقال إلى المزيد من القضايا الملحة.

6) mapN


mapN هي وظيفة مساعدة مفيدة للعمل مع tuples. مرة أخرى ، هذه ليست حداثة ، ولكنها بديل للمشغل القديم الجيد |@| إنه صراخ.

إليك ما تشبهه mapN في حالة وجود عنصرين:

 // where t2: Tuple2[F[A0], F[A1]] def mapN[Z](f: (A0, A1) => Z)(implicit functor: Functor[F], semigroupal: Semigroupal[F]): F[Z] = Semigroupal.map2(t2._1, t2._2)(f) 

في جوهرها ، يسمح لنا بتعيين قيم داخل tuple من أي F تمثل مجموعة شبه (منتج) وعامل توجيه (خريطة). لذلك:

 import cats.implicits._ ("a".some, "b".some).mapN(_ ++ _) //Some(ab) (List(1, 2), List(3, 4), List(0, 2).mapN(_ * _ * _)) //List(0, 6, 0, 8, 0, 12, 0, 16) 

بالمناسبة ، لا تنس أنه مع القطط تحصل على خريطة وخريطة leftmap :

 ("a".some, List("b","c").mapN(_ ++ _)) //won't compile, because outer type is not the same ("a".some, List("b", "c")).leftMap(_.toList).mapN(_ ++ _) //List(ab, ac) 

وظيفة .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._ //interchangable with eg Monix's Task type Query[T] = IO[Option[T]] def defineMead(qName: Query[String], qHoneyRatio: Query[Double], qAgingYears: Query[Double]): Query[Mead] = (for { name <- OptionT(qName) honeyRatio <- OptionT(qHoneyRatio) agingYears <- OptionT(qAgingYears) } yield Mead(name, honeyRatio, agingYears)).value def defineMead2(qName: Query[String], qHoneyRatio: Query[Double], qAgingYears: Query[Double]): Query[Mead] = for { name <- qName honeyRatio <- qHoneyRatio agingYears <- qAgingYears } yield (name, honeyRatio, agingYears).mapN(Mead) 

الطرق لها نتائج مماثلة ، لكن الأخير يستغني عن المحولات الأحادية.

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 //Some(Right(aaa)) 

بالإضافة إلى 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") //"Got error code 400" 

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] //Right[Int, String](a) 

من ناحية أخرى ، للحصول على Option.pure عليك استيراد cats.syntax.monad AND cats.instances.option :

 import cats.syntax.applicative._ import cats.instances.option._ "a".pure[Option] //Some(a) 

عن طريق تحسين عملية الاستيراد يدويًا ، ستحد من النطاقات الضمنية في ملفات Scala الخاصة بك وبالتالي تقلل من وقت الترجمة.

ومع ذلك ، يرجى: عدم القيام بذلك إذا لم يتم استيفاء الشروط التالية:

  • لقد أتقنت بالفعل القطط بشكل جيد
  • يمتلك فريقك المكتبة في نفس المستوى

لماذا؟ للأسباب التالية:

 //  ,   `pure`, //    import cats.implicits._ import cats.instances.option._ "a".pure[Option] //could not find implicit value for parameter F: cats.Applicative[Option] 

وذلك لأن كل من 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 بنشاط. خذ القطط كمثال. إليك فقط أحدث التغييرات:


لذلك ، عند العمل مع المشاريع ، لا تنس التحقق من إصدار المكتبة ، وقراءة الملاحظات للإصدارات الجديدة والتحديث في الوقت المناسب.

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


All Articles