سؤال

هذا السؤال لا يُقصد به مثل طعم اللهب! كما قد يكون واضحًا ، كنت أنظر إلى Scalaz مؤخرا. انا احاول ان افهم لماذا أحتاج إلى بعض الوظائف التي توفرها المكتبة. هذا شيء:

import scalaz._
import Scalaz._
type NEL[A] = NonEmptyList[A]
val NEL = NonEmptyList

وضعت بعض عبارات println في وظيفاتي لمعرفة ما يجري (جانبا: ماذا كنت سأفعل لو كنت أحاول تجنب آثار جانبية من هذا القبيل؟). وظائفي هي:

val f: NEL[Int] => String    = (l: NEL[Int]) => {println("f: " + l); l.toString |+| "X" }
val g: NEL[String] => BigInt = (l: NEL[String]) => {println("g: " + l);  BigInt(l.map(_.length).sum) }

ثم أنا أجمعهم عبر أ Cokleisli ويمر في أ NEL[Int]

val k = cokleisli(f) =>= cokleisli(g)
println("RES: "  + k( NEL(1, 2, 3) ))

ماذا هذه الطباعة؟

f: NonEmptyList(1, 2, 3)
f: NonEmptyList(2, 3)
f: NonEmptyList(3)
g: NonEmptyList(NonEmptyList(1, 2, 3)X, NonEmptyList(2, 3)X, NonEmptyList(3)X)
RES: 57

قيمة RES هي عدد أحرف العناصر (السلسلة) في NEL النهائية. شيئان يحدثان لي:

  1. كيف كان يمكن أن أعرف أن نيل الخاص بي سيتم تخفيضه بهذه الطريقة من توقيعات الطريقة المعنية؟ (لم أكن أتوقع النتيجة على الاطلاق)
  2. ما الغاية من هذا؟ هل يمكن تقطير حالة الاستخدام البسيطة وسهلة المتابعة بشكل معقول بالنسبة لي؟

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

هل كانت مفيدة؟

المحلول

لفهم النتيجة ، تحتاج إلى فهم Comonad[NonEmptyList] نموذج. Comonad[W] يوفر بشكل أساسي ثلاث وظائف (الواجهة الفعلية في Scalaz مختلفة قليلاً ، ولكن هذا يساعد في التفسير):

map:    (A => B) => W[A] => W[B]
copure: W[A] => A
cojoin: W[A] => W[W[A]]

لذا، Comonad يوفر واجهة لبعض الحاوية W يحتوي على عنصر "رأس" متميز (copure) وطريقة لتعريض الهيكل الداخلي للحاوية حتى نحصل على حاوية واحدة لكل عنصر (cojoin) ، كل مع عنصر معين في الرأس.

الطريقة التي يتم بها تنفيذ هذا NonEmptyList هل هذا copure إرجاع رأس القائمة ، و cojoin إرجاع قائمة القوائم ، مع هذه القائمة في الرأس وجميع ذيول هذه القائمة في الذيل.

مثال (أنا أقصر NonEmptyList ل Nel):

Nel(1,2,3).copure = 1
Nel(1,2,3).cojoin = Nel(Nel(1,2,3),Nel(2,3),Nel(3))

ال =>= الوظيفة هي تكوين cokleisli. كيف يمكنك تأليف وظيفتين f: W[A] => B و g: W[B] => C, ، لا تعرف شيئًا عنهم بخلاف ذلك W هو Comonad؟ نوع الإدخال من f ونوع الإخراج من g غير متوافق. ومع ذلك ، يمكنك map(f) لتأخذ، لتمتلك W[W[A]] => W[B] ثم تأليف ذلك مع g. الآن ، بالنظر إلى أ W[A], ، تستطيع cojoin للحصول على W[W[A]] للتغذية في هذه الوظيفة. لذلك ، التكوين المعقول الوحيد هو وظيفة k هذا ما يلي:

k(x) = g(x.cojoin.map(f))

لذلك لقائمة غير فارغة:

g(Nel(1,2,3).cojoin.map(f))
= g(Nel(Nel(1,2,3),Nel(2,3),Nel(3)).map(f))
= g(Nel("Nel(1,2,3)X","Nel(2,3)X","Nel(3)X"))
= BigInt(Nel("Nel(1,2,3)X","Nel(2,3)X","Nel(3)X").map(_.length).sum)
= BigInt(Nel(11,9,7).sum)
= 27

نصائح أخرى

يتم تعريف cojoin أيضا ل Scalaz.tree و scalaz.treeloc. هذا يمكن أن يكون مستغل للعثور على تيار من جميع المسارات من جذر الشجرة إلى كل عقدة ورقة.

def leafPaths[T](tree: Tree[T]): Stream[Stream[T]]
  = tree.loc.cojoin.toTree.flatten.filter(_.isLeaf).map(_.path)

باستخدام تكوين سهم Cokleisli ، يمكننا القيام بذلك ، على سبيل المثال:

def leafDist[A] = (cokleisli(leafPaths[A]) &&& cokleisli(_.rootLabel))
  =>= (_.map(s => (s._2, s._1.map(_.length).max)))

leafDist يأخذ شجرة ويعيد نسخة منها مع كل عقدة مشروحة مع أقصى مسافة لها من ورقة.

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