Записи - це структури даних, які обʼєднують повʼязану інформацію під мітками. Вони схожі на обʼєкти в 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 }
У цій вправі ми - великі фанати спорту, які щойно відкрили для себе пристрасть до баскетболу НБА. Оскільки ми новачки в баскетболі НБА, ми глибоко занурюємося в історію НБА: відстежуємо команди, тренерів, їхню статистику перемог і поразок та порівнюємо їх між собою.
Оскільки в нас ще немає улюбленої команди, ми також розробимо алгоритм, який допоможе визначити, чи варто вболівати за ту чи іншу команду. На нас чекає сім завдань, які допоможуть розробити власний алгоритм уболівання за команду.
Визначте псевдонім типу Coach для запису з двома такими полями, у такому порядку:
name: імʼя тренера, типу String.formerPlayer: показує, чи був тренер колишнім гравцем, типу Bool.Визначте псевдонім типу Stats для запису з двома такими полями, у такому порядку:
wins: кількість перемог, типу Int.losses: кількість поразок, типу Int.Визначте псевдонім типу Team для запису з трьома такими полями, у такому порядку:
name: назва команди, типу String.coach: тренер команди, типу Coach.stats: статистика команди, типу Stats.Нам потрібно взаємодіяти зі стороннім кодом, у якому є поняття команди, але який викликає наш код із параметрами в іншому порядку. Додайте функцію createTeam із такою сигнатурою String -> Stats -> Coach -> Team.
Власники клубів НБА нетерплячі, тож ми помітили, що погані результати команди часто призводять до заміни тренера.
Реалізуйте функцію 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 } }
У майбутньому ми плануємо створити складний алгоритм, який визначатиме, чи варто вболівати за команду, але для першої спроби ми вирішили вболівати за будь-яку команду, у якої більше перемог, ніж поразок.
Ми хочемо, щоб цю функцію можна було повторно використовувати в інших контекстах, тож використайте розширюваний запис у сигнатурі типу, щоб приймати будь-який запис із відповідними полями.
Щоб функція була якомога лаконічнішою, скористайтеся зіставленням зі зразком, щоб дістати 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
Зареєструйтеся на Exercism, щоб вивчати й опановувати Elm, а також 28 концепцій110 вправ та справжнє наставництво від людей, і все це безкоштовно.