مسیرها
/
Elm
Elm
/
تمرین‌ها
/
همراه موج
همراه موج

همراه موج

تمرین یادگیری

مقدمه

رکورد

«رکورد» یک ساختار داده است که اطلاعات مرتبط را با برچسب در کنار هم گروه‌بندی می‌کند. این‌ها شبیه به شیءها در JavaScript یا Java یا ساختارها در 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. مدل را تعریف کنید

نام مستعار نوع Coach را برای یک رکورد با دو فیلد زیر، به ترتیب زیر تعریف کنید:

  • name: اسم مربی، از نوع String.
  • formerPlayer: نشان می‌دهد که آیا مربی بازیکن پیشین بوده است، از نوع Bool.

نام مستعار نوع Stats را برای یک رکورد با دو فیلد زیر، به ترتیب زیر تعریف کنید:

  • wins: تعداد بردها، از نوع Int.
  • losses: تعداد باخت‌ها، از نوع Int.

نام مستعار نوع 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

آماده‌اید همراه موج را شروع کنید؟

در Exercism ثبت‌نام کنید تا Elm را همراه با 28 مفهوم110 تمرین و مربی‌گری انسانی واقعی یاد بگیرید و در آن استاد شوید، همه‌ی این‌ها رایگان.