أسرار المطبخ جافا سكريبت: التوابل

ألق نظرة على مقتطفات الشفرة التالية التي تحل نفس المشكلة ، وفكر في أيها تفضل.
هنا هو الأول:هنا هو الثاني:
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10] .filter(int => isEven(int)) .filter(int => isBiggerThan(3, int)) .map(int => int + 1) .map(int => toChar(int)) .filter(char => !isVowel(char)) .join('') // 'fhjl' 
 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] .filter(isEven) .filter(isBiggerThan(3)) .map(plus(1)) .map(toChar) .filter(not(isVowel)) .join('') // 'fhjl' 
يقول مؤلف المادة ، التي ننشر ترجمتها اليوم: "أراهن على أن الخيار الثاني هو قراءة أفضل بكثير من الخيار الأول". وفقا له ، فإن النقطة كلها في حجج filter() وطرق map() .



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

وظيفة بسيطة


خذ بعين الاعتبار دالة sum() بسيطة تضيف الأرقام التي تم تمريرها إليها:

 const sum = (a, b) => a + b sum(1, 2) // 3 

نعيد كتابتها ، مع إعطاء الوظيفة الجديدة اسم csum() :

 const csum = a => b => a + b csum(1)(2) // 3 

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

 csum(1) // b => 1 + b 

تؤدي هذه الاستدعاء إلى csum() إلى حقيقة أنها تُرجع دالة يمكنها قبول الوسيطة العددية الثانية التي تم تمريرها إلى csum() أثناء استدعائها المعتاد ، وترجع نتيجة إضافة واحدة إلى هذه الوسيطة. استدعاء هذه الوظيفة plusOne() :

 const plusOne = csum(1) plusOne(2) // 3 

العمل مع المصفوفات


في JavaScript ، يمكنك العمل مع المصفوفات باستخدام مجموعة متنوعة من الطرق الخاصة. لنفترض أن طريقة map() تُستخدم لتطبيق الوظيفة التي يتم تمريرها إليها على كل عنصر من عناصر المصفوفة.

على سبيل المثال ، من أجل زيادة كل عنصر في صفيف صحيح بمقدار 1 (بشكل أكثر دقة ، لتشكيل صفيف جديد يحتوي على عناصر الصفيف الأصلي زيادة بمقدار 1) ، يمكنك استخدام البناء التالي:

 [1, 2, 3].map(x => x + 1) // [2, 3, 4] 

بمعنى آخر ، يمكن وصف ما يحدث على النحو التالي: تأخذ الدالة x => x + 1 عددًا صحيحًا وتعرض الرقم الذي يليه في سلسلة من الأعداد الصحيحة. باستخدام وظيفة plusOne() الموضحة أعلاه ، يمكن إعادة كتابة هذا المثال على النحو التالي:

 [1, 2, 3].map(x => plusOne(x)) // [2, 3, 4] 

هنا من المفيد أن تتباطأ وتفكر في ما يحدث. إذا قمت بذلك ، يمكنك أن ترى أنه في الحالة قيد النظر ، تكون الإنشاءات x => plusOne(x) و plusOne (لاحظ أنه في هذه الحالة لا توجد أقواس بعد اسم الوظيفة) متكافئة. لفهم هذا بشكل أفضل ، ضع في الاعتبار otherPlusOne() :

 const otherPlusOne = x => plusOne(x) otherPlusOne(1) // 2 

ستكون نتيجة هذه الوظيفة هي نفس النتيجة التي تم الحصول عليها عن طريق استدعاء بسيط لـ plusOne() المعروف لنا بالفعل:

 plusOne(1) // 2 

ولنفس السبب ، يمكننا الحديث عن معادلة البنائين التاليين. هنا أول واحد شاهدناه بالفعل:

 [1, 2, 3].map(x => plusOne(x)) // [2, 3, 4] 

هنا هو الثاني:

 [1, 2, 3].map(plusOne) // [2, 3, 4] 

بالإضافة إلى ذلك ، تذكر كيف تم إنشاء دالة plusOne() :

 const plusOne = csum(1) 

هذا يسمح لنا بإعادة كتابة البناء map() النحو التالي:

 [1, 2, 3].map(csum(1)) // [2, 3, 4] 

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

 const isBiggerThan = (threshold, int) => int > threshold [1, 2, 3, 4].filter(int => isBiggerThan(3, int)) 

ثم ، للتخلص من كل شيء غير ضروري ، نحصل على الرمز الذي رأيته بالفعل في بداية هذه المادة:

 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] .filter(isEven) .filter(isBiggerThan(3)) .map(plus(1)) .map(toChar) .filter(not(isVowel)) .join('') // 'fhjl' 

نعتبر الآن قاعدتين بسيطتين تسمح لك بكتابة التعليمات البرمجية بالأسلوب الذي نوقش هنا.

القاعدة رقم 1


الهيكلان التاليان متكافئان:

 […].map(x => fnc(x)) […].map(fnc) 

القاعدة رقم 2


يمكن دائمًا إعادة كتابة الاسترجاعات لتقليل عدد الحجج المستخدمة للاتصال بها:

 const fnc = (x, y, z) => … […].map(x => fnc(x, y, z)) const fnc = (y, z) => x => … […].map(fnc(y, z)) 

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

 const isBiggerThan = (threshold, int) => int > threshold […].filter(int => isBiggerThan(3, int)) 

الآن نعيد كتابة الدالة isBiggerThan() بحيث يمكن استخدامها في طريقة filter() ولا نستخدم int=> بناء:

 const isBiggerThan = threshold => int => int > threshold […].map(isBiggerThan(3)) 

تمرن


افترض أن لدينا جزء التعليمات البرمجية التالي:

 const keepGreatestChar = (char1, char2) => char1 > char2 ? char1 : char2 keepGreatestChar('b', 'f') // 'f' //   'f'   'b' 

الآن ، بناءً على keepGreatestChar() ، قم بإنشاء دالة keepGreatestCharBetweenBAnd() . نحن بحاجة إلى ذلك ، من خلال تسميتها ، يمكننا تمرير وسيطة واحدة فقط لها ، بينما ستقارن الشخصية التي تم تمريرها إليها بالحرف b . قد تبدو هذه الوظيفة كما يلي:

 const keepGreatestChar = (char1, char2) => char1 > char2 ? char1 : char2 const keepGreatestCharBetweenBAnd = char => keepGreatestChar('b', char) keepGreatestCharBetweenBAnd('a') // 'b' //   'b'   'a' 

اكتب الآن دالة keepGreatestChar() ، والتي تسمح لك باستخدام keepGreatestChar() في طريقة الصفيف reduce() بالبحث عن الحرف "الأكبر" ولا تحتاج إلى وسيطات. لنبدأ بهذا الرمز:

 const keepGreatestChar = (char1, char2) => char1 > char2 ? char1 : char2 const greatestCharInArray = array => array.reduce((acc, char) => acc > char ? acc : char, 'a') greatestCharInArray(['a', 'b', 'c', 'd']) // 'd' 

لحل هذه المشكلة ، قم بتنفيذ creduce() ، التي يمكن استخدامها في الدالة creduce() ، والتي تسمح ، في التطبيق العملي لهذه الوظيفة ، بعدم تمرير أي شيء إليها باستثناء المصفوفة التي تجد فيها الحرف الذي يحتوي على أكبر رمز.

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

 const greatestCharInArray = creduce(keepGreatestChar, 'a') greatestCharInArray(['a', 'b', 'c', 'd']) // 'd' 

الملخص


ربما لديك الآن سؤال حول السبب في أن الطرق ، التي تتم معالجتها وفقًا للمنهجية المعروضة هنا ، لها أسماء تبدأ بالحرف c . إن الحرف c هو اختصار لـ curried ، وتحدثنا عن كيف تساعد الوظائف curried في تحسين قراءة التعليمات البرمجية. وتجدر الإشارة إلى أننا هنا لم نسعى جاهدين للالتزام الصارم بمبادئ البرمجة الوظيفية ، ولكننا نعتقد أن التطبيق العملي لما تمت مناقشته هنا يسمح لنا بتحسين الكود. إذا كان موضوع الكاري في جافا سكريبت ممتعًا بالنسبة لك - فمن المستحسن قراءة الفصل الرابع من هذا الكتاب حول البرمجة الوظيفية ، وبشكل عام ، منذ أن وصلت إلى هذه النقطة - اقرأ هذا الكتاب بالكامل. أيضًا ، إذا كنت جديدًا في البرمجة الوظيفية ، فراجع مادة البدء هذه .

أعزائي القراء! هل تستخدم وظيفة الكاري في تطوير جافا سكريبت؟

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


All Articles