سؤال

بعد المشاهدة عرض نيك باريددج على اشتقاق Scalaz, ، حصلت على النظر إلى هذا المثال ، وهو رائع فقط:

import scalaz._
import Scalaz._
def even(x: Int) : Validation[NonEmptyList[String], Int] 
    = if (x % 2 ==0) x.success else "not even: %d".format(x).wrapNel.fail

println( even(3) <|*|> even(5) ) //prints: Failure(NonEmptyList(not even: 3, not even: 5))

كنت أحاول أن أفهم ما <|*|> الطريقة كانت تفعل ، هنا هو الكود المصدر:

def <|*|>[B](b: M[B])(implicit t: Functor[M], a: Apply[M]): M[(A, B)] 
    = <**>(b, (_: A, _: B))

حسنًا ، هذا مربك إلى حد ما (!) - لكنه يشير إلى <**> الطريقة التي تم الإعلان عنها هكذا:

def <**>[B, C](b: M[B], z: (A, B) => C)(implicit t: Functor[M], a: Apply[M]): M[C] 
    = a(t.fmap(value, z.curried), b)

لذلك لدي بعض الأسئلة:

  1. كيف يبدو أن الطريقة تأخذ ملف نوع أعلى من معلمة نوع واحد (M[B]) ولكن يمكن أن يتم تمرير أ Validation (أيهما يمتلكان نوعان من الطوائف)؟
  2. بناء الجملة (_: A, _: B) يحدد الوظيفة (A, B) => Pair[A,B] التي تتوقعها الطريقة الثانية: ما الذي يحدث لـ Tuple2/الزوج في حالة الفشل؟ ليس هناك توب في الأفق!
هل كانت مفيدة؟

المحلول

نوع المنشآت كمعلمات نوع

M هي معلمة نوع إلى أحد القوادين الرئيسيين في Scalaz ، م, ، يمثل هذا مُنشئ النوع (ويعرف أيضًا باسم النوع الأعلى) من القيمة المملوءة. يتم استخدام هذا النوع من المُنشئ للبحث عن الحالات المناسبة لـ Functor و Apply, ، وهي متطلبات ضمنية للطريقة <**>.

trait MA[M[_], A] {
   val value: M[A]
   def <**>[B, C](b: M[B], z: (A, B) => C)(implicit t: Functor[M], a: Apply[M]): M[C] = ...
}

ما هو نوع مُنشئ؟

من مرجع لغة سكالا:

نحن نميز بين أنواع الترتيب الأول وممناءات النوع ، والتي تأخذ معلمات النوع وأنواع العائد. تمثل مجموعة فرعية من أنواع الدرجة الأولى التي تسمى أنواع القيمة مجموعات من قيم (من الدرجة الأولى). أنواع القيمة إما ملموسة أو مجردة. يمكن تمثيل كل نوع قيمة ملموس مع التحسين (§3.2.7) الذي يقيد أنواع أعضاءها. يتم تقديم أنواع القيمة التجريدية بواسطة معلمات النوع (§4.4) وترابط النوع التجريدي (§4.3). يتم استخدام الأقواس في الأنواع للتجميع. نحن نفترض أن الكائنات والحزم أيضًا تحدد ضمنيًا فئة (بنفس اسم الكائن أو الحزمة ، ولكن لا يمكن الوصول إليها لبرامج المستخدم).

الأنواع غير القيمة تلتقط خصائص المعرفات التي ليست قيمًا (§3.3). على سبيل المثال ، لا يحدد منشئ النوع (§3.3.3) مباشرة نوع القيم. ومع ذلك ، عندما يتم تطبيق مُنشئ النوع على وسيطات النوع الصحيحة ، فإنه ينتج عنه نوع من الدرجة الأولى ، والذي قد يكون نوعًا من القيمة. يتم التعبير عن أنواع غير القيمة بشكل غير مباشر في Scala. على سبيل المثال ، يتم وصف نوع الطريقة عن طريق تدوين توقيع الطريقة ، والذي في حد ذاته ليس نوعًا حقيقيًا ، على الرغم من أنه يؤدي إلى نوع وظيفة مقابلة (§3.3.1). تعد منشئي النوع مثالًا آخر ، حيث يمكن للمرء أن يكتب Swap [M [_ ، _] ، a ، b] = m [b ، a] ، ولكن لا يوجد بناء جملة لكتابة وظيفة النوع المجهول المقابلة مباشرة.

List هو منشئ النوع. يمكنك تطبيق النوع Int للحصول على نوع القيمة ، List[Int], والتي يمكن أن تصنف القيمة. يأخذ منشئو النوع الآخر أكثر من معلمة واحدة.

السمة scalaz.MA يتطلب أن تكون المعلمة من النوع الأول يجب أن تكون مُنشئ نوعًا يأخذ نوعًا واحدًا لإرجاع نوع القيمة ، مع بناء الجملة trait MA[M[_], A] {}. يصف تعريف المعلمة النوع شكل مُنشئ النوع ، والذي يشار إليه على أنه نوعه. List يقال أن يكون له النوع* -> *.

التطبيق الجزئي للأنواع

ولكن كيف يمكن MA لف قيم النوع Validation[X, Y]؟ نوع Validation لديه نوع (* *) -> *, ، ولا يمكن تمريرها إلا كوسيطة نوع إلى معلمة نوع تم الإعلان عنها مثل M[_, _].

هذا التحويل الضمني في كائن Scalaz يحول قيمة النوع Validation[X, Y] إلى MA:

object Scalaz {
    implicit def ValidationMA[A, E](v: Validation[E, A]): MA[PartialApply1Of2[Validation, E]#Apply, A] = ma[PartialApply1Of2[Validation, E]#Apply, A](v)
}

والتي بدورها تستخدم خدعة مع نوع الاسم المستعار في partialapply1of2 لتطبيق جزئيًا على مُنشئ النوع Validation, ، إصلاح نوع الأخطاء ، ولكن ترك نوع النجاح غير مطبق.

PartialApply1Of2[Validation, E]#Apply سيكون من الأفضل كتابة [X] => Validation[E, X]. لقد اقترحت مؤخرًا إضافة مثل هذا الجملة إلى Scala ، وقد يحدث ذلك في 2.9.

فكر في هذا على أنه مستوى يعادل هذا:

def validation[A, B](a: A, b: B) = ...
def partialApply1Of2[A, B C](f: (A, B) => C, a: A): (B => C) = (b: B) => f(a, b)

هذا يتيح لك الجمع Validation[String, Int] مع Validation[String, Boolean], ، لأن كلاهما يشتركان في منشئ النوع [A] Validation[String, A].

الفوشرات التطبيقية

<**> يطالب بمنشئ النوع M يجب أن يكون لديك حالات مرتبطة يتقدم و functor. هذا يشكل functor تطبيق ، والذي ، مثل الموناد ، هو وسيلة لتنظيم حساب من خلال بعض التأثير. في هذه الحالة ، يكون التأثير هو أن الحوسبة الفرعية يمكن أن تفشل (وعندما تفعل ذلك ، نتراكم الفشل).

الحاوية Validation[NonEmptyList[String], A] يمكن أن يلف قيمة نقية من النوع A في هذا "التأثير". ال <**> يأخذ المشغل قيمتين فعالتين ، ووظيفة خالصة ، ويجمع بينهما مع مثيل functor التطبيقي لتلك الحاوية.

إليك كيفية عملها ل Option functor التطبيقي. "التأثير" هنا هو إمكانية الفشل.

val os: Option[String] = Some("a")
val oi: Option[Int] = Some(2)

val result1 = (os <**> oi) { (s: String, i: Int) => s * i }
assert(result1 == Some("aa"))

val result2 = (os <**> (None: Option[Int])) { (s: String, i: Int) => s * i }
assert(result2 == None)

في كلتا الحالتين ، هناك وظيفة نقية من النوع (String, Int) => String, ، يجري تطبيقه على الحجج الفعالة. لاحظ أن النتيجة ملفوفة في نفس التأثير (أو الحاوية ، إذا أردت) ، مثل الوسائط.

يمكنك استخدام نفس النمط عبر العديد من الحاويات التي تحتوي على functor المطلي المرتبط. جميع الموناد هم من الفطريات التطبيقية تلقائيًا ، ولكن هناك المزيد ، مثل ZipStream.

Option و [A]Validation[X, A] كلا الموناد ، لذلك يمكنك أيضا استخدام Bind (AKA Flatmap):

val result3 = oi flatMap { i => os map { s => s * i } }
val result4 = for {i <- oi; s <- os} yield s * i

tupling مع `<| ** |>`

<|**|> يشبه حقًا <**>, ، لكنه يوفر الوظيفة النقية لك ببساطة بناء tuple2 من النتائج. (_: A, _ B) هو اختصار ل (a: A, b: B) => Tuple2(a, b)

وما وراءها

إليك أمثلةنا المجمعة لـ تطبيق و تصديق. لقد استخدمت بناء جملة مختلف قليلاً لاستخدام المسلح التطبيقي ، (fa ⊛ fb ⊛ fc ⊛ fd) {(a, b, c, d) => .... }

تحديث: ولكن ماذا يحدث في حالة الفشل؟

ما يحدث لـ tuple2/الزوج في حالة الفشل?

في حالة فشل أي من الحوسبة الفرعية ، يتم تشغيل الوظيفة المقدمة أبدًا. يتم تشغيله فقط إذا كانت جميع الحواسبات الفرعية (في هذه الحالة ، تم تمرير الوسيطتين <**>) ناجحة. إذا كان الأمر كذلك ، فهو يجمع بين هذه في Success. أين هذا المنطق؟ هذا يحدد Apply مثال على [A] Validation[X, A]. نطلب أن يكون لدى النوع X Semigroup Avaiable ، وهي استراتيجية للجمع بين الأخطاء الفردية ، كل نوع من النوع X, ، في خطأ مجمعة من نفس النوع. إذا اخترت String كنوع الخطأ الخاص بك ، Semigroup[String] يسلط السلاسل. إذا اخترت NonEmptyList[String], ، الخطأ (الأخطاء) من كل خطوة متسلسلة إلى فترة أطول NonEmptyList من الأخطاء. يحدث هذا التسلسل أدناه عندما اثنان Failures يتم دمجها ، باستخدام المشغل (الذي يتوسع مع ضمن ضمن ، على سبيل المثال ، Scalaz.IdentityTo(e1).⊹(e2)(Semigroup.NonEmptyListSemigroup(Semigroup.StringSemigroup)).

implicit def ValidationApply[X: Semigroup]: Apply[PartialApply1Of2[Validation, X]#Apply] = new Apply[PartialApply1Of2[Validation, X]#Apply] {
  def apply[A, B](f: Validation[X, A => B], a: Validation[X, A]) = (f, a) match {
    case (Success(f), Success(a)) => success(f(a))
    case (Success(_), Failure(e)) => failure(e)
    case (Failure(e), Success(_)) => failure(e)
    case (Failure(e1), Failure(e2)) => failure(e1 ⊹ e2)
  }
}

موناد أو تطبيقي ، كيف أختار؟

لا أزال أقرأ؟ ((نعم. إد)

لقد أظهرت أن الحوسبة الفرعية تستند إلى Option أو [A] Validation[E, A] يمكن دمجها مع أي منهما Apply أو مع Bind. متى ستختار واحدة على الآخر؟

عندما تستخدم Apply, ، تم إصلاح هيكل الحساب. سيتم تنفيذ جميع الحوسبة الفرعية ؛ نتائج المرء لا يمكن أن تؤثر على الآخرين. فقط الوظيفة "النقية" لديها نظرة عامة على ما حدث. الحسابات الأحادية ، من ناحية أخرى ، تسمح بالتكوين الفرعي الأول للتأثير على الحالات اللاحقة.

إذا استخدمنا بنية التحقق من صحة أحادي ، فإن الفشل الأول سيخضع لدائرة التحقق بأكمله ، حيث لن يكون هناك لا Success قيمة للتغذية في التحقق اللاحق. ومع ذلك ، يسعدنا أن تكون القياس الفرعي مستقلة ، حتى نتمكن من الجمع بينها من خلال التطبيقي ، وجمع جميع الإخفاقات التي نواجهها. أصبح ضعف المسلسلات التطبيقية قوة!

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top