يسرع التماثل إلى الإنقاذ

"التماثل" هو أحد المفاهيم الأساسية للرياضيات الحديثة. باستخدام أمثلة ملموسة في Haskell و C # ، لن أشرح نظرية لغير علماء الرياضيات فقط (دون استخدام أي رموز ومصطلحات رياضية غامضة) ، لكنني سأوضح أيضًا كيف يمكن استخدام ذلك في الممارسة اليومية.


المشكلة هي أن المساواة الصارمة (على سبيل المثال ، 2 + 2 = 4) غالبًا ما تكون صارمة. هنا مثال:


هاسكل
add :: (a, a) -> a add (x, y) = x + y 

C #
 int Add(Tuple<int, int> pair) { return pair.Item1 + pair.Item2; } 

ومع ذلك ، هناك طريقة واحدة أكثر تعقيدًا وفي كثير من الحالات عملية أكثر - لتحديد الوظيفة نفسها بمعنى ما :


هاسكل
 add' :: a -> a -> a add' x = \y -> x + y 

C #
 Func<int, int> Add_(int x) { return y => x + y; } 

على عكس الحقيقة الواضحة المتمثلة في أنه بالنسبة إلى أي x x ، فإن كلتا الدالتين ستعود دائمًا إلى نفس النتيجة ، فهي لا تحقق المساواة الصارمة:


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

وهذا "صارم للغاية".


التماثل هو "صارم جدا". لا يتطلب مساواة كاملة وشاملة للجميع ، ولكنه يقتصر على المساواة "بمعنى ما" ، والتي يتم تحديدها دائمًا في سياق محدد.


كما قد تخمن ، كلا التعاريف المذكورة أعلاه غير متجانسة. هذا يعني بالضبط ما يلي: إذا تم إعطاء واحد منهم فقط لي ، فسيتم إعطائي كليهما ضمنيًا : كل ​​ذلك بفضل التماثل - المحول ثنائي الاتجاه لأحدهما الآخر . تلخيص الأنواع قليلا:


هاسكل
 curry :: ((a, b) → c) → a → b → c curry fxy = f (x, y), uncurry :: (a → b → c) → (a, b) → c uncurry f (x, y) = fxy 

C #
 Func<TArg1, Func<TArg2, TRes>> Curry(Func<Tuple<TArg1, TArg2>, TRes> uncurried) { return arg1 => arg2 => uncurried(Tuple.Create(arg1, arg2)); } Func<Tuple<TArg1, TArg2>, TRes> Uncurry(Func<TArg1, Func<TArg2, TRes>> curried) { return pair => curried(pair.Item1)(pair.Item2); } 

... والآن عن أي س ، ص :


هاسكل
 curry add $ x, y = uncurry add' $ (x, y) 

C #
 Curry(Add)(x)(y) = Uncurry(Add_)(Tuple.Create(x, y)) 

أكثر قليلا الرياضيات للفضوليين خاصة

في الواقع ، يجب أن يبدو مثل هذا:


هاسكل
 curry . uncurry = id uncurry . curry = id id x = x 

C #
 Compose(Curry, Uncurry) = Id Compose(Uncurry, Curry) = Id, : T Id<T>(T arg) => arg; Func<TArg, TFinalRes> Compose<TArg, TRes, TFinalRes>( Func<TArg, TRes> first, Func<TRes, TFinalRes> second) { return arg => second(first(arg)); } ...  extension- (  Id   ): Curry.Compose(Uncurry) = Id Uncurry.Compose(Curry) = Id, : public static Func<TArg, TFinalRes> Compose<TArg, TRes, TFinalRes>( this Func<TArg, TRes> first, Func<TRes, TFinalRes> second) { return arg => second(first(arg)); } 

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


بالإضافة إلى ذلك ، هناك نظرية مفادها أن عنصر المعرف فريد دائمًا. لاحظ أن وظيفة الهوية عامة ، متعددة الأشكال ، وبالتالي فريدة حقًا فيما يتعلق بكل نوع معين.


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


هاسكل
 toLazy :: a -> () -> a toLazy x = \_ -> a fromLazy :: (() -> a) -> a fromLazy f = f () 

C #
 Func<TRes> Lazy(TRes res) { return () => res; } TRes Lazy(Func<TRes> lazy) { return lazy(); } 

هذا التماثل يحافظ على نتيجة الحساب المؤجل نفسه - وهذا هو "خاصية مهمة" ، في حين أن هياكل البيانات مختلفة.


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


ملاحظة: إذا رأيت أن المقالة استفادت ، فسوف أعود إلى موضوعات "الحلول المعمارية الأكثر نجاحًا (الدقيقة)" و "النهج المنقح للاختبار".

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


All Articles