التفكير الوظيفي. الجزء 9

هذا بالفعل جزء 9 من سلسلة من المقالات حول البرمجة الوظيفية في F #! أنا متأكد من أنه لا يوجد الكثير من هذه الدورات الطويلة في حبري. لكننا لن نتوقف. اليوم سنتحدث عن الوظائف المتداخلة والوحدات ومساحات الأسماء وخلط الأنواع والوظائف في الوحدات النمطية.






أنت الآن تعرف كيفية تحديد الوظائف ، ولكن كيفية تنظيمها؟


يحتوي F # على ثلاثة خيارات:


  • وظائف يمكن أن تكون متداخلة في وظائف أخرى.
  • على مستوى التطبيق ، يتم تجميع وظائف المستوى الأعلى في "وحدات".
  • أو يمكنك اتباع نهج موجه للكائنات وإرفاق الدالات بأنواع كطرق.

في هذه المقالة ، سننظر في أول طريقتين ، والطريقة المتبقية في التالي.


وظائف متداخلة


في F # ، يمكنك تحديد وظائف داخل وظائف أخرى. هذه طريقة جيدة لتغليف الوظائف المساعدة المطلوبة فقط للوظيفة الرئيسية ويجب ألا تكون مرئية من الخارج.


في المثال أدناه ، add متداخلة في addThreeNumbers :


 let addThreeNumbers xyz = //     let add n = fun x -> x + n //    x |> add y |> add z addThreeNumbers 2 3 4 

يمكن للوظائف المتداخلة الوصول إلى المعلمات الأصل مباشرة ، لأنها في نطاقها.
لذلك ، في المثال أدناه ، printError تحتاج الدالة المتداخلة printError إلى معلمات ، لأن يمكنها الوصول إلى n و max مباشرة.


 let validateSize max n = //       let printError() = printfn "Oops: '%i' is bigger than max: '%i'" n max //    if n > max then printError() validateSize 10 9 validateSize 10 11 

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


 let sumNumbersUpTo max = //      let rec recursiveSum n sumSoFar = match n with | 0 -> sumSoFar | _ -> recursiveSum (n-1) (n+sumSoFar) //       recursiveSum max 0 sumNumbersUpTo 10 

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


مثال على كيفية عدم القيام:


 // wtf,    ? let fx = let f2 y = let f3 z = x * z let f4 z = let f5 z = y * z let f6 () = y * x f6() f4 y x * f2 x 

وحدات


الوحدة النمطية هي ببساطة مجموعة من الوظائف التي يتم تجميعها معًا ، عادةً لأنها تعمل مع نفس نوع البيانات أو أنواعها.


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


تعريف وحدة تحتوي على وظيفتين:


 module MathStuff = let add xy = x + y let subtract xy = x - y 

إذا قمت بفتح هذا الرمز في Visual Studio ، فعند التمرير فوق add يمكنك رؤية add الكاملة للاسم ، وهي في الواقع MathStuff.add ، كما لو كان MastStuff فئة ، وكانت add طريقة.


في الواقع ، هذا هو بالضبط ما يحدث. خلف الكواليس ، ينشئ برنامج التحويل البرمجي F # فئة ثابتة بطرق ثابتة. سيبدو المكافئ C # هكذا:


 static class MathStuff { static public int add(int x, int y) { return x + y; } static public int subtract(int x, int y) { return x - y; } } 

إن إدراك أن الوحدات النمطية عبارة عن فئات ثابتة فقط وأن الوظائف عبارة عن طرق ثابتة ستعطي فكرة جيدة عن كيفية عمل الوحدات النمطية في F # ، نظرًا لأن معظم القواعد التي تنطبق على الفئات الثابتة تنطبق على الوحدات النمطية أيضًا.


وكما هو الحال في C # ، يجب أن تكون كل وظيفة قائمة بذاتها جزءًا من الفصل ، في F # ، يجب أن تكون كل وظيفة قائمة بذاتها جزءًا من الوحدة النمطية.


الوصول إلى وظائف خارج الوحدة النمطية


إذا كنت بحاجة إلى الوصول إلى وظيفة من وحدة أخرى ، يمكنك الرجوع إليها من خلال اسمها الكامل.


 module MathStuff = let add xy = x + y let subtract xy = x - y module OtherStuff = //     MathStuff let add1 x = MathStuff.add x 1 

يمكنك أيضًا استيراد جميع وظائف وحدة أخرى باستخدام التوجيه open ، وبعد ذلك يمكنك استخدام الاسم المختصر بدلاً من الاسم الكامل.


 module OtherStuff = open MathStuff //      let add1 x = add x 1 

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


وحدات متداخلة


مثل الفئات الثابتة ، يمكن أن تحتوي الوحدات النمطية على وحدات متداخلة:


 module MathStuff = let add xy = x + y let subtract xy = x - y //   module FloatLib = let add xy :float = x + y let subtract xy :float = x - y 

قد تشير الوحدات الأخرى إلى وظائف في وحدات متداخلة باستخدام الاسم الكامل أو النسبي ، حسب الاقتضاء:


 module OtherStuff = open MathStuff let add1 x = add x 1 //   let add1Float x = MathStuff.FloatLib.add x 1.0 //   let sub1Float x = FloatLib.subtract x 1.0 

وحدات المستوى الأعلى


وبالتالي ، نظرًا لأنه يمكن تداخل الوحدات النمطية ، وبالتالي ، في الصعود إلى السلسلة ، يمكنك الوصول إلى وحدة الأصل من المستوى الأعلى. انها حقا.


يتم تعريف الوحدات ذات المستوى الأعلى بشكل مختلف ، على عكس الوحدات التي تم عرضها سابقًا.


  • يجب أن يكون module MyModuleName هو أول إعلان في الملف
  • علامة = مفقود
  • يجب ألا يتم وضع مسافة بادئة لمحتوى الوحدة

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


بالنسبة لملفات .FSX ، لا يلزم تعريف الوحدة النمطية ، وفي هذه الحالة يصبح اسم ملف البرنامج النصي تلقائيًا هو اسم الوحدة النمطية.


مثال على إعلان MathStuff كوحدة نمطية "الوحدة العليا":


 //    module MathStuff let add xy = x + y let subtract xy = x - y //   module FloatLib = let add xy :float = x + y let subtract xy :float = x - y 

لاحظ أنه لا يوجد مسافة بادئة في كود "المستوى الأعلى" ( module MathStuff ) ، في حين لا يزال يتعين تعيين محتويات وحدة FloatLib المتداخلة.


محتويات وحدة أخرى


بالإضافة إلى الوظائف ، يمكن أن تحتوي الوحدات النمطية على إعلانات أخرى ، مثل تعريفات الكتابة والقيم البسيطة ورمز التهيئة (على سبيل المثال ، المنشئات الثابتة)


 module MathStuff = //  let add xy = x + y let subtract xy = x - y //   type Complex = {r:float; i:float} type IntegerFunction = int -> int -> int type DegreesOrRadians = Deg | Rad // "" let PI = 3.141 // "" let mutable TrigType = Deg //  /   do printfn "module initialized" 

بالمناسبة ، إذا قمت بتشغيل هذه الأمثلة بشكل تفاعلي ، فقد تحتاج إلى إعادة تشغيل الجلسة في كثير من الأحيان بحيث تظل الشفرة "جديدة" ولا تتأثر بالعمليات الحسابية السابقة.


إخفاء (التداخل ، التظليل)


هذا هو مرة أخرى وحدة عينة لدينا. لاحظ أن MathStuff يحتوي على وظيفة add وكذلك FloatLib .


 module MathStuff = let add xy = x + y let subtract xy = x - y //   module FloatLib = let add xy :float = x + y let subtract xy :float = x - y 

ماذا يحدث إذا قمت بفتح كلتا الوحدتين في النطاق الحالي ونداء add ؟


 open MathStuff open MathStuff.FloatLib let result = add 1 2 // Compiler error: This expression was expected to // have type float but here has type int 

وحدث أن وحدة MathStuff.FloatLib أعادت تعريف MathStuff الأصلية ، والتي تم حظرها (مخفية) بواسطة وحدة FloatLib .


نتيجة لذلك ، حصلنا على خطأ برنامج التحويل البرمجي FS0001 ، لأن المعلمة الأولى 1 كانت متوقعة. لإصلاح ذلك ، يجب عليك تغيير 1 إلى 1.0 .


لسوء الحظ ، في الممارسة العملية يتم تجاهل هذا بسهولة وسرية . في بعض الأحيان باستخدام هذه التقنية ، يمكنك القيام ببعض الحيل المثيرة للاهتمام ، مثل الفئات الفرعية تقريبًا ، ولكن غالبًا ما يكون وجود وظائف لها نفس الاسم مزعجًا (على سبيل المثال ، في حالة وظيفة map الشائعة للغاية).


إذا كنت تريد تجنب هذا السلوك ، فهناك طريقة لإيقافه باستخدام سمة RequireQualifiedAccess . نفس المثال الذي تزين به كلتا الوحدتين بهذه السمة:


 [<RequireQualifiedAccess>] module MathStuff = let add xy = x + y let subtract xy = x - y //   [<RequireQualifiedAccess>] module FloatLib = let add xy :float = x + y let subtract xy :float = x - y 

الآن التوجيه open غير متوفر:


 open MathStuff //  open MathStuff.FloatLib //  

ولكن لا يزال بإمكانك الوصول إلى الوظائف (دون أي غموض) من خلال أسمائها الكاملة:


 let result = MathStuff.add 1 2 let result = MathStuff.FloatLib.add 1.0 2.0 

التحكم في الوصول


يدعم F # استخدام عوامل التحكم في الوصول إلى .NET القياسية مثل public private internal . تحتوي المقالة MSDN على معلومات كاملة.


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

تعد محددات الوصول هذه طريقة واحدة للتحكم في الرؤية في F #. هناك طريقة مختلفة تمامًا تتمثل في استخدام ملفات التوقيع التي تشبه ملفات رأس C. فهي تصف محتويات الوحدة بشكل تجريدي. التواقيع مفيدة للغاية للتغليف الخطير ، ولكن للنظر في قدراتها ، سيتعين عليك انتظار السلسلة المخططة بشأن التغليف والأمان استنادًا إلى القدرات .


مساحات الأسماء


تشبه مساحات الأسماء في F # مساحات الأسماء من C #. يمكن استخدامها لتنظيم الوحدات النمطية وأنواعها لتجنب تعارض الأسماء.


تم تعريف namespace باستخدام الكلمة الأساسية namespace :


 namespace Utilities module MathStuff = //  let add xy = x + y let subtract xy = x - y 

بسبب مساحة الاسم هذه ، أصبح الاسم الكامل MathStuff Utilities.MathStuff ، والاسم الكامل هو MathStuff Utilities.MathStuff.add .


تنطبق قواعد المسافة البادئة نفسها على الوحدات النمطية ضمن مساحة الاسم التي تم عرضها أعلاه للوحدات النمطية.


يمكنك أيضًا إعلان مساحة اسم بشكل صريح عن طريق إضافة فترة في اسم الوحدة النمطية. أي يمكن إعادة كتابة الكود أعلاه كما يلي:


 module Utilities.MathStuff //  let add xy = x + y let subtract xy = x - y 

لا يزال الاسم الكامل MathStuff Utilities.MathStuff ، ولكنه الآن وحدة نمطية من المستوى الأعلى ومحتوياتها لا تحتاج إلى المسافة البادئة.


بعض الميزات الإضافية لاستخدام مساحات الأسماء:


  • مساحات الأسماء اختيارية للوحدات النمطية. بخلاف C # ، بالنسبة لمشاريع F # ، لا توجد مساحة اسم افتراضية ، لذلك ستكون الوحدة النمطية ذات المستوى الأعلى بدون مساحة الاسم عمومية. إذا كنت تخطط لإنشاء مكتبات قابلة لإعادة الاستخدام ، فيجب عليك إضافة العديد من مساحات الأسماء لتجنب التعارض مع رمز المكتبات الأخرى.
  • يمكن أن تحتوي مساحات الأسماء مباشرة على تعريفات الكتابة ، ولكن لا يمكن أن تعمل على تعريفات الإعلانات. كما ذكر سابقًا ، يجب أن تكون كل إعلانات القيمة والوظائف جزءًا من وحدة نمطية.
  • أخيرًا ، ضع في اعتبارك أن مساحات الأسماء لا تعمل في البرامج النصية. على سبيل المثال ، إذا حاولت إرسال إعلان مساحة اسم ، مثل namespace Utilities ، إلى نافذة تفاعلية ، يتم تلقي خطأ.

مساحة التسلسل الهرمي


يمكنك إنشاء تسلسل هرمي لمساحات الأسماء ببساطة عن طريق تقسيم الأسماء بالنقاط:


 namespace Core.Utilities module MathStuff = let add xy = x + y 

يمكنك أيضًا إعلان مساحات الأسماء في ملف واحد إذا كنت تريد ذلك. تجدر الإشارة إلى أنه يجب الإعلان عن جميع مساحات الأسماء بأسمائها الكاملة - فهي لا تدعم التداخل.


 namespace Core.Utilities module MathStuff = let add xy = x + y namespace Core.Extra module MoreMathStuff = let add xy = x + y 

لا يمكن تعارض الاسم بين مساحة الاسم والوحدة النمطية.


 namespace Core.Utilities module MathStuff = let add xy = x + y namespace Core //    - Core.Utilities //     ! module Utilities = let add xy = x + y 

خلط الأنواع والوظائف في الوحدات النمطية


كما رأينا ، تتكون الوحدات النمطية عادة من العديد من الوظائف المترابطة التي تتفاعل مع نوع بيانات معين.


في OOP ، سيتم دمج هياكل البيانات والوظائف الموجودة فوقها معًا داخل الفصل. وفي وظيفية F # ، يتم دمج هياكل البيانات والوظائف الموجودة فوقها في وحدة نمطية.


هناك نمطان لدمج الأنواع والوظائف معًا:


  • أعلن نوع بشكل منفصل عن وظائف
  • تم إعلان النوع في نفس الوحدة مثل الوظائف

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


 //    namespace Example //      type PersonType = {First:string; Last:string} //    ,     module Person = //  let create first last = {First=first; Last=last} // ,     let fullName {First=first; Last=last} = first + " " + last let person = Person.create "john" "doe" Person.fullName person |> printfn "Fullname=%s" 

بدلاً من ذلك ، يتم الإعلان عن النوع داخل الوحدة النمطية وله اسم بسيط مثل " T " أو اسم الوحدة النمطية. الوصول إلى الوظائف تقريبًا كما يلي: MyModule.Func و MyModule.Func2 ، والوصول إلى النوع: MyModule.T :


 module Customer = // Customer.T -      type T = {AccountId:int; Name:string} //  let create id name = {T.AccountId=id; T.Name=name} // ,     let isValid {T.AccountId=id; } = id > 0 let customer = Customer.create 42 "bob" Customer.isValid customer |> printfn "Is valid?=%b" 

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


إذن أي طريقة للاختيار؟


  • تشبه الطريقة الأولى .NET الكلاسيكية ، ويجب تفضيلها إذا كنت تخطط لاستخدام هذه المكتبة لرمز خارج F # ، حيث من المتوقع وجود فصل موجود بشكل منفصل.
  • النهج الثاني هو أكثر شيوعا في لغات وظيفية أخرى. يجمع النوع الموجود داخل الوحدة النمطية كفئة متداخلة ، والتي عادةً ما تكون غير ملائمة للغات OOP.

لنفسك ، يمكنك تجربة مع كلتا الطريقتين. في حالة تطوير الفريق ، يجب اختيار نمط واحد.


وحدات تحتوي على أنواع فقط


إذا كان هناك العديد من الأنواع التي تحتاج إلى الإعلان دون أي وظائف ، فلا تهتم باستخدام الوحدة. يمكنك تعريف الأنواع مباشرة في مساحة الاسم دون اللجوء إلى فئات متداخلة.


على سبيل المثال ، قد ترغب في القيام بذلك:


 //    module Example //     type PersonType = {First:string; Last:string} //    ,  ... 

وهنا طريقة أخرى لفعل الشيء نفسه. module استبدال module الكلمة ببساطة بمساحة namespace .


 //    namespace Example //     type PersonType = {First:string; Last:string} 

في كلتا الحالتين ، سيكون لدى PersonType نفس الاسم الكامل.


يرجى ملاحظة أن هذا الاستبدال يعمل فقط مع الأنواع. يجب دائمًا الإعلان عن الوظائف داخل الوحدة.


موارد إضافية


هناك العديد من البرامج التعليمية لـ F # ، بما في ذلك المواد لأولئك الذين يأتون مع تجربة C # أو Java. قد تكون الروابط التالية مفيدة كلما تعمقت في F #:



موصوفة أيضًا عدة طرق أخرى لبدء التعلم F # .


أخيرًا ، يعتبر مجتمع F # صديقًا للمبتدئين جدًا. هناك دردشة نشطة للغاية في Slack ، بدعم من F # Software Foundation ، مع غرف مبتدئين يمكنك الانضمام إليها بحرية . نحن نوصي بشدة أن تفعل هذا!


لا تنسَ زيارة موقع مجتمع الناطقين بالروسية F # ! إذا كانت لديك أي أسئلة حول تعلم اللغة ، فسيسعدنا مناقشتها في غرف الدردشة:



حول مؤلفي الترجمة


ترجم بواسطة kleidemos
تم إجراء تغييرات في التحرير والتحرير من خلال جهود مجتمع الناطقين باللغة الروسية من مطوري F # . نشكر أيضًا @ schvepsss و @ shwars على إعداد هذه المقالة للنشر.

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


All Articles