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

الاستخدام الجزئي للوظائف
فكرة التطبيق الجزئي هي أنه إذا قمنا بإصلاح أول معلمات N للدالة ، فإننا نحصل على وظيفة جديدة مع المعلمات المتبقية. من مناقشة الكاري ، يمكن للمرء أن يرى كيف يحدث التطبيق الجزئي بشكل طبيعي.
بعض الأمثلة البسيطة لتوضيح:
// "" + 42 let add42 = (+) 42 // add42 1 add42 3 // // [1;2;3] |> List.map add42 // "" let twoIsLessThan = (<) 2 // twoIsLessThan 1 twoIsLessThan 3 // twoIsLessThan [1;2;3] |> List.filter twoIsLessThan // "" printfn let printer = printfn "printing param=%i" // printer [1;2;3] |> List.iter printer
في كل حالة ، نقوم بإنشاء وظيفة مطبقة جزئيًا يمكن إعادة استخدامها في مواقف مختلفة.
وبالطبع ، فإن التطبيق الجزئي يجعل من السهل بنفس القدر إصلاح معلمات الوظيفة. إليك بعض الأمثلة:
// List.map let add1 = (+) 1 let add1ToEach = List.map add1 // "add1" List.map // add1ToEach [1;2;3;4] // List.filter let filterEvens = List.filter (fun i -> i%2 = 0) // // filterEvens [1;2;3;4]
يوضح المثال التالي الأكثر تعقيدًا كيف يمكن استخدام نفس النهج لخلق سلوك "مضمن" بشفافية.
- نقوم بإنشاء دالة تجمع رقمين ، ولكنها بالإضافة إلى ذلك ، تأخذ وظيفة تسجيل تقوم بتسجيل هذه الأرقام والنتيجة.
- تأخذ وظيفة التسجيل معلمتين: (سلسلة) "name" و (عام) "value" ، وبالتالي فهي تحتوي على
string->'a->unit
التوقيع string->'a->unit
. - ثم نقوم بإنشاء تطبيقات مختلفة لوظيفة التسجيل ، مثل مسجل وحدة التحكم أو المسجل القائم على النوافذ المنبثقة.
- وأخيرًا ، نقوم بتطبيق الوظيفة الرئيسية جزئيًا لإنشاء وظيفة جديدة ، باستخدام مسجل مغلق.
// - let adderWithPluggableLogger logger xy = logger "x" x logger "y" y let result = x + y logger "x+y" result result // - let consoleLogger argName argValue = printfn "%s=%A" argName argValue // let addWithConsoleLogger = adderWithPluggableLogger consoleLogger addWithConsoleLogger 1 2 addWithConsoleLogger 42 99 // - let popupLogger argName argValue = let message = sprintf "%s=%A" argName argValue System.Windows.Forms.MessageBox.Show( text=message,caption="Logger") |> ignore // - let addWithPopupLogger = adderWithPluggableLogger popupLogger addWithPopupLogger 1 2 addWithPopupLogger 42 99
يمكن استخدام وظائف المسجل المغلق مثل أي وظائف أخرى. على سبيل المثال ، يمكننا إنشاء تطبيق جزئي لإضافة 42 ، ثم تمريره إلى وظيفة القائمة ، كما فعلنا مع وظيفة add42
البسيطة.
// 42 let add42WithConsoleLogger = addWithConsoleLogger 42 [1;2;3] |> List.map add42WithConsoleLogger [1;2;3] |> List.map add42 //
تعتبر الوظائف المطبقة جزئيًا أداة مفيدة جدًا. يمكننا إنشاء وظائف مكتبة مرنة (وإن كانت معقدة) ، ومن السهل جعلها قابلة لإعادة الاستخدام بشكل افتراضي ، بحيث يتم إخفاء التعقيد عن رمز العميل.
تصميم الوظائف الجزئية
من الواضح أن ترتيب المعلمات يمكن أن يؤثر بشكل خطير على راحة الاستخدام الجزئي. على سبيل المثال ، معظم الوظائف في List
مثل List.map
و List.filter
لها شكل مشابه ، وهي:
List-function [function parameter(s)] [list]
القائمة هي المعلمة الأخيرة دائمًا. بعض الأمثلة في شكل كامل:
List.map (fun i -> i+1) [0;1;2;3] List.filter (fun i -> i>1) [0;1;2;3] List.sortBy (fun i -> -i ) [0;1;2;3]
نفس الأمثلة باستخدام التطبيق الجزئي:
let eachAdd1 = List.map (fun i -> i+1) eachAdd1 [0;1;2;3] let excludeOneOrLess = List.filter (fun i -> i>1) excludeOneOrLess [0;1;2;3] let sortDesc = List.sortBy (fun i -> -i) sortDesc [0;1;2;3]
إذا تم تنفيذ وظائف المكتبة بترتيب مختلف من الحجج ، فسيكون التطبيق الجزئي أقل ملاءمة.
عندما تكتب وظيفتك مع العديد من المعلمات ، يمكنك التفكير في أفضل ترتيب لها. كما هو الحال مع جميع قضايا التصميم ، لا توجد إجابة "صحيحة" ، ولكن هناك العديد من التوصيات المقبولة بشكل عام.
- ابدأ بالخيارات التي من المحتمل أن تكون ثابتة.
- كن الأخير لتعيين هياكل البيانات أو المجموعات (أو معلمات متغيرة أخرى)
- لفهم أفضل للعمليات مثل الطرح ، من المستحسن مراقبة الترتيب المتوقع
النصيحة الأولى بسيطة. يجب أن تبدأ المعلمات التي من المحتمل أن يتم "إصلاحها" بواسطة التطبيق الجزئي أولاً ، كما هو الحال في الأمثلة مع المسجل أعلاه.
اتباع الطرف الثاني يجعل من السهل استخدام مشغل الأنابيب وتركيبها. لقد رأينا هذا بالفعل عدة مرات في الأمثلة مع وظائف أعلاه القوائم.
// let result = [1..10] |> List.map (fun i -> i+1) |> List.filter (fun i -> i>5)
وبالمثل ، فإن الوظائف التطبيقية الجزئية على القوائم تتعرض بسهولة للتكوين ، لأن يمكن حذف معلمة القائمة:
let compositeOp = List.map (fun i -> i+1) >> List.filter (fun i -> i>5) let result = compositeOp [1..10]
التفاف جزئي لوظيفة BCL
يمكن الوصول بسهولة إلى وظائف مكتبة الفئة الأساسية (BCL) لـ .NET من F # ، ولكن تم تصميمها دون الاعتماد على اللغات الوظيفية مثل F #. على سبيل المثال ، تتطلب معظم الوظائف معلمة بيانات في البداية ، بينما في F # يجب أن تكون معلمة البيانات هي الأخيرة بشكل عام.
ومع ذلك ، يمكنك بسهولة كتابة الأغلفة لجعل هذه الوظائف أكثر اصطلاحية. في المثال أدناه ، تتم إعادة كتابة دالات السلسلة .NET بحيث يتم استخدام السلسلة الهدف أخيرًا بدلاً من الأولى:
// .NET let replace oldStr newStr (s:string) = s.Replace(oldValue=oldStr, newValue=newStr) let startsWith lookFor (s:string) = s.StartsWith(lookFor)
بعد أن تصبح السلسلة المعلمة الأخيرة ، يمكنك استخدام هذه الوظائف في خطوط الأنابيب ، كالمعتاد:
let result = "hello" |> replace "h" "j" |> startsWith "j" ["the"; "quick"; "brown"; "fox"] |> List.filter (startsWith "f")
أو في تكوين الوظائف:
let compositeOp = replace "h" "j" >> startsWith "j" let result = compositeOp "hello"
فهم عامل الناقل
بعد أن ترى تطبيقًا جزئيًا في الأعمال التجارية ، يمكنك أن تفهم كيف تعمل وظائف الأنابيب.
يتم تعريف وظيفة الأنابيب على النحو التالي:
let (|>) xf = fx
كل ما تفعله هو الجدل قبل الوظيفة ، وليس بعدها.
let doSomething xyz = x+y+z doSomething 1 2 3 //
في الحالة التي تحتوي فيها الدالة f
على العديد من المعلمات ، وستكون المعلمة الأخيرة للدالة f
بمثابة قيمة الإدخال x
لخط الأنابيب. في الواقع ، تم بالفعل تطبيق الوظيفة المنقولة f
جزئيًا وتتوقع معلمة واحدة فقط - قيمة الإدخال لخط الأنابيب (te x
).
هنا مثال مماثل أعيد كتابته للاستخدام الجزئي.
let doSomething xy = let intermediateFn z = x+y+z intermediateFn // intermediateFn let doSomethingPartial = doSomething 1 2 doSomethingPartial 3 // 3 |> doSomethingPartial // ,
كما رأيت ، فإن عامل تشغيل الأنابيب هو أمر شائع للغاية في F # ، ويستخدم كلما أردت الحفاظ على التدفق الطبيعي للبيانات. بعض الأمثلة الأخرى التي قد تكون صادفتها:
"12" |> int // "12" int 1 |> (+) 2 |> (*) 3 //
مشغل ناقل عكسي
من وقت لآخر ، قد تواجه عامل خط الأنابيب العكسي "<|".
let (<|) fx = fx
يبدو أن هذه الوظيفة لا تفعل شيئًا ، فلماذا توجد؟
والسبب هو أنه عند استخدام مشغل خط الأنابيب العكسي كعامل ثنائي بنمط infix ، فإنه يقلل من الحاجة إلى الأقواس ، مما يجعل الشفرة أكثر نظافة.
printf "%i" 1+2 // printf "%i" (1+2) // printf "%i" <| 1+2 //
يمكنك استخدام خطوط الأنابيب في اتجاهين في آن واحد للحصول على تدوين مزيف.
let add xy = x + y (1+2) add (3+4) // 1+2 |> add <| 3+4 //
موارد إضافية
هناك العديد من البرامج التعليمية لـ F # ، بما في ذلك المواد لأولئك الذين يأتون مع تجربة C # أو Java. قد تكون الروابط التالية مفيدة عندما تتعمق في F #:
كما تم وصف عدة طرق أخرى لبدء تعلم F # .
أخيرًا ، مجتمع F # ودود للغاية للمبتدئين. هناك محادثة نشطة للغاية في Slack ، تدعمها F # Software Foundation ، مع غرف للمبتدئين يمكنك الانضمام إليها بحرية . نوصي بشدة أن تفعل ذلك!
لا تنس زيارة موقع المجتمع الناطق باللغة الروسية F # ! إذا كان لديك أي أسئلة حول تعلم لغة ، يسعدنا مناقشتها في غرف الدردشة:
حول مؤلفي الترجمة
ترجمه kleidemos
تم إجراء تغييرات الترجمة والتحرير من خلال جهود المجتمع الناطق باللغة الروسية لمطوري F # . نشكر أيضًا schvepsss و shwars لإعداد هذه المقالة للنشر.