المسارات
/
Elm
Elm
/
التمارين
/
صندوق الكنز الآمن
صندوق الكنز الآمن

صندوق الكنز الآمن

تمرين تعلّمي

مقدمة

الأنواع المعتمة

مقدمة

الأنواع المعتمة أسلوب من أساليب التغليف في Elm. ويسمّي بعض الناس هذا أيضًا إخفاء المعلومات، أو إخفاء تفاصيل التنفيذ. وهي شائعة أيضًا لفرض التحقق، وذلك باستخدام نمط حلّل، لا تتحقّق. على سبيل المثال، يمكن أن يكون لديك نوع معتم EmailAddress لا يمكن أن يحتوي إلا على عناوين بريد إلكتروني صالحة (على عكس String، الذي يمكن أن يحتوي على أي شيء).

إنشاء نوع معتم

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

type Maybe a
    = Nothing --> Variant implies a 'Nothing' function, with type 'Maybe a', and destructuring of 'Nothing'
    | Just a  --> Variant implies a 'Just' function, with type 'a -> Maybe a', and destructuring of 'Just _'

افتراضيًا، يكون كل هذا داخليًا في الملف أو الوحدة، ونستخدم الكلمة المفتاحية exposing لاختيار ما نصدّره. ولإنشاء نوع معتم، كل ما عليك هو أن تختار عدم تصدير الصيغ.

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

حلّل، لا تتحقّق

لعل أكثر استخدامات الأنواع المعتمة شيوعًا هو فرض التحقق، باستخدام نمط حلّل، لا تتحقّق. في هذا النمط، تصدّر دالة تأخذ مُدخلًا ببنية أو أمان أنواع أقل مما تريد، مثل String، ثم تحلّله إلى نوع أكثر بنية، مثل EmailAddress، فإما تُرجع النوع، وإما تُرجع خطأ (عادةً Maybe أو Result).

على سبيل المثال، في الكود أدناه، لا يمكن للوحدات الخارجية إنشاء نوع EmailAddress إلا باستخدام الدالة parseEmailAddress، لذا عندما تتعامل مع نوع EmailAddress، تكون واثقًا من أنه صالح.

module EmailAddress exposing ( EmailAddress, parseEmailAddress, getEmailAddress )

-- The `EmailAddress` custom type has one variant, also called `EmailAddress`
-- When a custom type has one variant, it is idiomatic to use the same name for both.
type EmailAddress = 
  EmailAddress String

-- create an EmailAddress variant
parseEmailAddress: String -> Maybe EmailAddress
parseEmailAddress candidateEmailAddress =   
  if isValidEmailAddress candidateEmailAddress then
    Just (EmailAddress candidateEmailAddress)
  else
    Nothing

-- get an EmailAddress variant
getEmailAddress: EmailAddress -> String
getEmailAddress ( EmailAddress  emailAdress ) =
  emailAdress

isValidEmailAddress: String -> bool
-- ... 

التعليمات

في هذا التمرين ستكتب SecureTreasureChest عامًّا (وسحريًّا!) لتخزين بعض الكنوز. لعلّ الطلاب ذوي العين الثاقبة لاحظوا أن TreasureChest السابق لم يكن بهذه الأمانة! إذا استخدمت دالة getTreasure، كان عليك تقديم كلمة المرور، لكن كان بإمكانك مع ذلك تفكيك بنية النوع TreasureChest للحصول على الكنز، دون أن تعرف كلمة المرور. والأسوأ من ذلك، كان بإمكانك حتى استرجاع كلمة المرور بالطريقة نفسها! لنُصلح ذلك باستخدام نوع معتم.

1. تعريف النوع المعتم Password باستخدام نمط "Parse, don't validate"

عرّف نوعًا مخصصًا Password ببديل واحد.

ينبغي أن يكون للبديل قيمة String مرتبطة به، تمثل كلمة المرور.

ينبغي أن تكشف عن النوع Password، لكن ليس عن البديل. وهذا يجعل Password نوعًا معتمًا، ويعني أنك لم تعد تستطيع استخدام تفكيك البنية للحصول على كلمة المرور. ليس ممكنًا اختبار ما إذا كان النوع معتمًا أم لا؛ في كود الإنتاج ستستخدم elm-review (أداة تحليل ساكن) لفرض ذلك.

عرّف دالة createPassword واكشف عنها، مع تعريف نوع، تأخذ String وتُرجع Maybe Password.

توصي Owasp بأن تكون كلمات المرور بطول 8 أحرف على الأقل، لذا لنستخدم نمط Parse, don't validate لفرض هذا الشرط الثابت.

إذا كانت كلمة المرور أقل من 8 أحرف، فينبغي أن تُرجع createPassword القيمة Nothing، وإلا فينبغي أن تُرجع Just Password.

2. اكشف عن SecureTreasureChest، لكن ليس بديله

ينبغي أن تكشف عن النوع SecureTreasureChest، لكن ليس عن البديل. وهذا يجعل SecureTreasureChest نوعًا معتمًا، ويعني أنك لم تعد تستطيع استخدام تفكيك البنية للحصول على كلمة المرور أو الكنز.

3. تعريف دالة createTreasure

ينبغي أن تأخذ هذه الدالة معاملين اثنين، وأن يكون لها تعريف نوع.

  • نوع عام (للكنز)
  • Password (لكلمة المرور)

ينبغي أن تُرجع الدالة SecureTreasureChest.

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

4. تعريف دالة getTreasure

ينبغي أن تأخذ هذه الدالة معاملين اثنين، وأن يكون لها تعريف نوع.

  • String (لكلمة المرور)
  • نوع مخصص عام SecureTreasureChest

ينبغي أن تتحقق هذه الدالة من محاولة كلمة المرور المُقدَّمة مقابل Password الموجودة في SecureTreasureChest.

ينبغي أن تُرجع الدالة قيمة Maybe.

إذا تطابقت كلمتا المرور فأرجع Just بالقيمة العامة من SecureTreasureChest (الكنز!)

إذا لم تتطابق كلمتا المرور فأرجع Nothing.

اكشف عن هذه الدالة، حتى تتمكن الوحدات الأخرى من الحصول على الكنز من SecureTreasureChest، ما دامت تقدّم كلمة المرور الصحيحة.

تعديل عبر GitHub يفتح الرابط في نافذة أو علامة تبويب جديدة
Elm Exercism

مستعد لبدء صندوق الكنز الآمن؟

سجّل في Exercism لتتعلّم وتتقن Elm عبر 28 مفهومًا110 تمارين، وإرشاد بشري حقيقي، وكل ذلك مجانًا.