Bandwagoner

Bandwagoner

تمرين تعلّمي

مقدمة

السجلات

السجلات هياكل بيانات تجمع معًا معلومات مترابطة تحت تسميات. وهي مشابهة للكائنات في JavaScript أو Java، أو بنى struct في C أو Rust، لكن مع بعض الفروق الجوهرية.

إنشاء السجلات

تُنشأ السجلات باستخدام الأقواس المعقوفة، وتُفصل عناصرها بفواصل.

firefly =
    { name = "Firefly"
    , creator = "Joss Whedon"
    , episodes = 14
    }

يُعرَّف نوع السجل أيضًا بصيغة مشابهة، باستثناء أن علامات يساوي = تُستبدل بعلامات النقطتين :.

firefly : { name : String, creator : String, episodes : Int }
firefly =
    { name = "Firefly"
    , creator = "Joss Whedon"
    , episodes = 14
    }

تكرار تعريفات الأنواع هذه أمر مُرهق، لذا يُعد استخدام اسم بديل للنوع لمشاركة التعريف أسلوبًا شائعًا.

type alias TvSeries =
    { name : String
    , creator : String
    , episodes : Int
    }

firefly : TvSeries
firefly =
    { name = "Firefly"
    , creator = "Joss Whedon"
    , episodes = 14
    }

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

firefly : TvSeries
firefly = TvSeries "Firefly" "Joss Whedon" 14

الوصول إلى حقول السجل

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

firefly : TvSeries
firefly =
    { name = "Firefly"
    , creator = "Joss Whedon"
    , episodes = 14
    }

firefly.name
    --> "Firefly"

.creator firefly
    --> "Joss Whedon"

يمكن أيضًا تفكيك السجلات في الارتباطات باستخدام مطابقة أنماط السجلات. يعمل التفكيك مع أي سجل يحتوي على حقول بالأسماء المعنية.

episodesCount : TvSeries -> Int
episodesCount { episodes } =
    episodes

complicatedCopy : TvSeries -> TvSeries
complicatedCopy show =
    let
        { name, creator } = show
    in
    { name = name
    , creator = creator
    , episodes = episodesCount show
    }

تحديث السجلات

السجلات غير قابلة للتغيير، شأنها شأن كل شيء في Elm، لذا بمجرد تعريف سجل، لا يمكن أن تتغير قيم حقوله أبدًا. توجد صيغة خاصة لإنشاء نسخة من سجل موجود مع تغيير حقل واحد أو أكثر. تستخدم هذه الصيغة رمز الخط العمودي | للتمييز بين السجل الأصلي والحقول التي ستتغير، مثل { oldRecord | field1 = newValue }.

firefly : TvSeries
firefly =
    { name = "Firefly"
    , creator = "Joss Whedon"
    , episodes = 14
    }

updatedFirefly : TvSeries
updatedFirefly =
    { firefly | creator = "J Whedon", episodes = 15 }

مقارنة السجلات

يستخدم Elm [المساواة البنيوية][equality]، أي أن نسختين من السجل نفسه بقيم متطابقة تكونان متساويتين.

firefly1 = TvSeries "Firefly" "Joss Whedon" 14
firefly2 = TvSeries "Firefly" "Joss Whedon" 14

firefly1 == firefly2
    --> True

السجلات القابلة للتوسيع

يدعم Elm أيضًا التنميط البنيوي، بمعنى أنه إذا تطلّبت دالة سجلًا يحتوي على حقلَي x و y، فستعمل مع أي سجل يمتلك هذين الحقلين، مثل النقاط ثنائية الأبعاد والنقاط ثلاثية الأبعاد والمركبات الفضائية وما إلى ذلك. تحتوي أنواع السجلات هذه على خط عمودي | في تعريفها، مثل { a | x : Float, y : Float }، وتُسمى «السجلات القابلة للتوسيع» في مصطلحات Elm. احذر الفرق بين خط عمودي | في تعريف نوع، وهو تعريف سجل قابل للتوسيع ({ a | x : Int })، وخط عمودي | في نسخة سجل فعلية، وهو يعني أننا نحدّث بعض حقول ذلك السجل.

point2d = { x = 1, y = 2 }
point3d = { x = 3, y = 4, z = 7 }

.x point2d --> 1
.x point3d --> 3

length : { a | x : Float, y : Float } -> Float
length vector =
    sqrt (vector.x * vector.x + vector.y * vector.y)

length point2d --> 2.236068
length point3d --> 5

translateX : { a | x : Float } -> { a | x : Float }
translateX vector =
    { vector | x = vector.x + 1 }

translateX point2d --> { x = 2, y = 2 }
translateX point3d --> { x = 4, y = 4, z = 7 }

التعليمات

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

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

1. عرّف النموذج

عرّف type alias بالاسم Coach لسجل يحتوي الحقلين التاليين، بالترتيب التالي:

  • name: اسم المدرب، من النوع String.
  • formerPlayer: يشير إلى ما إذا كان المدرب لاعبًا سابقًا، من النوع Bool.

عرّف type alias بالاسم Stats لسجل يحتوي الحقلين التاليين، بالترتيب التالي:

  • wins: عدد الانتصارات، من النوع Int.
  • losses: عدد الخسائر، من النوع Int.

عرّف type alias بالاسم Team لسجل يحتوي الحقول الثلاثة التالية، بالترتيب التالي:

  • name: اسم الفريق، من النوع String.
  • coach: مدرب الفريق، من النوع Coach.
  • stats: إحصاءات الفريق، من النوع Stats.

2. أنشئ فريقًا

تريد التواصل مع كود طرف ثالث لديه مفهوم الفريق، لكنه ينادي الكود لديك بمعاملات بترتيب مختلف. أضف دالة createTeam بالتوقيع التالي String -> Stats -> Coach -> Team.

3. استبدل المدرب

نظرًا لأن مالكي فرق NBA قليلو الصبر، وجدت أن النتائج السيئة للفريق كثيرًا ما تؤدي إلى استبدال المدرب. نفّذ دالة replaceCoach التي تأخذ المدرب الجديد والفريق كمعاملين، وتُرجع الفريق مع المدرب الجديد:

let
    coach = { name = "Larry Bird", formerPlayer = True }
    stats = { wins = 58, losses =  24 }
    team = { name = "Indiana Pacers", coach = coach, stats =  stats }
    newCoach = { name = "Isiah Thomas", formerPlayer = True }
in
replaceCoach newCoach team
--> { name = "Indiana Pacers"
--  , coach = { name = "Isiah Thomas", formerPlayer = True }
--  , stats = { wins = 58, losses = 24 } }

4. تحقّق مما إذا كان ينبغي أن تشجّع فريقًا

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

تريد أن تكون هذه الدالة قابلة لإعادة الاستخدام في سياقات أخرى، لذا استخدم سجلًا قابلًا للتوسيع في توقيع النوع لقبول أي سجل يحتوي الحقول ذات الصلة. ولتبقى الدالة موجزة قدر الإمكان، تريد استخدام مطابقة الأنماط للحصول على stats من معامل الدالة. نفّذ دالة rootForTeam التي تأخذ فريقًا وتُرجع True إذا كان ينبغي أن تشجّع ذلك الفريق؛ وإلا فتُرجع False.

let
    spursCoach = { name = "Gregg Popovich", formerPlayer =  False }
    spursStats = { wins = 56, losses = 26 }
    spursTeam = { name = "San Antonio Spurs", coach = spursCoach, stats = spursStats }
in
rootForTeam spursTeam
--> True
تعديل عبر GitHub يفتح الرابط في نافذة أو علامة تبويب جديدة
Elm Exercism

مستعد لبدء Bandwagoner؟

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