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