Відповіді:
Як я думаю про це:
type
використовується для визначення нових типів об'єднання:
type Thing = Something | SomethingElse
До цього визначення Something
і SomethingElse
нічого не означав. Тепер вони обидва типу Thing
, які ми тільки що визначили.
type alias
використовується для надання імені іншому типу, який вже існує:
type alias Location = { lat:Int, long:Int }
{ lat = 5, long = 10 }
має тип { lat:Int, long:Int }
, який уже був дійсним типом. Але тепер ми можемо також сказати, що він має тип, Location
тому що це псевдонім для одного типу.
Варто зауважити, що наступне буде скомпільовано просто чудово та відображено "thing"
. Незважаючи на те, що ми вказуємо " thing
a" String
і " aliasedStringIdentity
приймає" AliasedString
, ми не отримаємо помилку про невідповідність типу String
/ AliasedString
:
import Graphics.Element exposing (show)
type alias AliasedString = String
aliasedStringIdentity: AliasedString -> AliasedString
aliasedStringIdentity s = s
thing : String
thing = "thing"
main =
show <| aliasedStringIdentity thing
{ lat:Int, long:Int }
не визначає нового типу. Це вже дійсний тип. type alias Location = { lat:Int, long:Int }
також не визначає новий тип, він просто дає інше (можливо, більш описове) ім'я вже дійсному типу. type Location = Geo { lat:Int, long:Int }
визначив би новий тип ( Location
)
Ключ - це слово alias
. У ході програмування, коли ви хочете згрупувати речі, що належать разом, ви ставите їх у запис, як у випадку точки
{ x = 5, y = 4 }
або запис студента.
{ name = "Billy Bob", grade = 10, classof = 1998 }
Тепер, якщо вам потрібно було передати ці записи навколо, вам доведеться прописати весь тип, наприклад:
add : { x:Int, y:Int } -> { x:Int, y:Int } -> { x:Int, y:Int }
add a b =
{ a.x + b.x, a.y + b.y }
Якби ви могли отримати псевдонім, підпис було б набагато простіше написати!
type alias Point = { x:Int, y:Int }
add : Point -> Point -> Point
add a b =
{ a.x + b.x, a.y + b.y }
Тож псевдонім - це скорочення для чогось іншого. Ось це скорочення для запису типу. Ви можете подумати про це як назву типу запису, який ви часто використовуєте. Ось чому його називають псевдонімом - це інша назва голого типу запису, яку представляє{ x:Int, y:Int }
Тоді як type
вирішується інша проблема. Якщо ви приїжджаєте з OOP, це проблема, яку ви вирішуєте з успадкуванням, перевантаженням оператора тощо. - Іноді ви хочете трактувати дані як загальну річ, а іноді ви хочете ставитися до них як до конкретної речі.
Поширене місце, коли це відбувається - це передавання повідомлень - наприклад, поштова система. Коли ви надсилаєте лист, ви хочете, щоб поштова система розглядала всі повідомлення як одне й те саме, тому вам потрібно розробити поштову систему лише один раз. Крім того, робота з маршрутизації повідомлення повинна бути незалежною від повідомлення, що міститься в ньому. Тільки коли лист досягне місця призначення, ви дбаєте про те, що таке повідомлення.
Таким же чином ми могли б визначити type
як об'єднання всіх різних типів повідомлень, які можуть статися. Скажімо, ми впроваджуємо систему обміну повідомленнями між студентами коледжу до їх батьків. Тож дітки з коледжу можуть надсилати лише два повідомлення: «Мені потрібні гроші на пиво» та «Мені потрібні труси».
type MessageHome = NeedBeerMoney | NeedUnderpants
Тож тепер, коли ми розробляємо систему маршрутизації, типи наших функцій можуть просто пройти навколо MessageHome
, замість того, щоб турбуватися про всі різні типи повідомлень, які вони можуть бути. Система маршрутизації не хвилює. Потрібно лише знати, що це MessageHome
. Лише коли повідомлення досягне місця призначення, батьківського дому, вам потрібно зрозуміти, що це таке.
case message of
NeedBeerMoney ->
sayNo()
NeedUnderpants ->
sendUnderpants(3)
Якщо ви знаєте архітектуру Elm, функція оновлення - це гігантський випадок, оскільки це місце, куди повідомлення надсилається, а отже, обробляється. І ми використовуємо типи об'єднань, щоб мати єдиний тип для вирішення при передачі повідомлення навколо, але потім можемо використовувати випадок випадку, щоб перевірити, яке саме повідомлення було, тому ми можемо з ним розібратися.
Дозвольте доповнити попередні відповіді, зосереджуючись на випадках використання та надаючи невеликий контекст функціям і модулям конструктора.
type alias
Створення псевдоніма та функції конструктора для запису
Це найпоширеніший варіант використання: ви можете визначити альтернативне ім'я та функцію конструктора для певного виду формату запису.
type alias Person =
{ name : String
, age : Int
}
Визначення псевдоніма типу автоматично передбачає наступну функцію конструктора (псевдокод):
Person : String -> Int -> { name : String, age : Int }
Це може стати в нагоді, наприклад, коли ви хочете написати декодер Json.
personDecoder : Json.Decode.Decoder Person
personDecoder =
Json.Decode.map2 Person
(Json.Decode.field "name" Json.Decode.String)
(Json.Decode.field "age" Int)
Вкажіть необхідні поля.
Іноді вони називають це "розширювані записи", що може ввести в оману. Цей синтаксис можна використовувати, щоб вказати, що ви очікуєте деякий запис із наявними певними полями. Як от:
type alias NamedThing x =
{ x
| name : String
}
showName : NamedThing x -> Html msg
showName thing =
Html.text thing.name
Тоді ви можете скористатися описаною вище функцією (наприклад, на ваш погляд):
let
joe = { name = "Joe", age = 34 }
in
showName joe
Розмова Річарда Фельдмана на ElmEurope 2017 може дати деяке подальше розуміння того, коли цей стиль варто використовувати.
Перейменування речей
Ви можете зробити це, тому що нові імена можуть надавати додаткове значення у вашому коді, як у цьому прикладі
type alias Id = String
type alias ElapsedTime = Time
type SessionStatus
= NotStarted
| Active Id ElapsedTime
| Finished Id
Можливо, кращим прикладом такого використання в ядрі єTime
.
Повторне опромінення типу з іншого модуля
Якщо ви пишете пакет (а не додаток), можливо, вам доведеться реалізувати тип в одному модулі, можливо, у внутрішньому (не піддається впливу) модулі, але ви хочете викрити тип з інший (загальнодоступний) модуль. Або, як альтернатива, ви хочете викрити свій тип з декількох модулів.
Task
в ядрі та Http.Request в Http - приклади першого, тоді як пара Json.Encode.Value і Json.Decode.Value є прикладом пізнішого.
Ви можете це зробити лише тоді, коли в іншому випадку хочете зберегти тип непрозорим: ви не піддаєте функції конструктора. Детальніше див. Звичаї type
нижче.
Варто зазначити, що у наведених вище прикладах лише №1 забезпечує функцію конструктора. Якщо ви виставите псевдонім свого типу в module Data exposing (Person)
такому №1 , це відкриє ім'я типу, а також функцію конструктора.
type
Визначте теговий тип об'єднання
Це найпоширеніший випадок використання, хорошим прикладом цього є Maybe
тип у ядрі :
type Maybe a
= Just a
| Nothing
Коли ви визначаєте тип, ви також визначаєте його функції конструктора. У випадку, можливо, це (псевдокод):
Just : a -> Maybe a
Nothing : Maybe a
Що означає, що якщо ви оголосите це значення:
mayHaveANumber : Maybe Int
Його можна створити будь-яким
mayHaveANumber = Nothing
або
mayHaveANumber = Just 5
В Just
і Nothing
теги , не тільки служать функції конструктора, вони також служать в якості деструкторів або візерунки в case
вираженні. Що означає, що за допомогою цих шаблонів ви можете бачити всередині Maybe
:
showValue : Maybe Int -> Html msg
showValue mayHaveANumber =
case mayHaveANumber of
Nothing ->
Html.text "N/A"
Just number ->
Html.text (toString number)
Ви можете це зробити, тому що модуль "Можливо" визначений як
module Maybe exposing
( Maybe(Just,Nothing)
Це також може сказати
module Maybe exposing
( Maybe(..)
Два в цьому випадку рівноцінні, але чітке вважається чеснотою в Elm, особливо коли ви пишете пакет.
Приховування деталей реалізації
Як було зазначено вище, це навмисний вибір, який функції конструктора Maybe
видимі для інших модулів.
Однак є й інші випадки, коли автор вирішує їх приховати. Одним із прикладів цього в основі єDict
. Будучи споживачем пакету, ви не повинні бачити деталі реалізації алгоритму Red / Black tree позаду Dict
та безладно зв’язуватися з вузлами. Приховування функцій конструктора змушує споживача вашого модуля / пакета створювати лише значення вашого типу (а потім трансформувати ці значення) за допомогою функцій, які ви відкриваєте.
З цієї причини іноді такі речі з'являються в коді
type Person =
Person { name : String, age : Int }
На відміну від type alias
визначення вгорі цієї публікації, цей синтаксис створює новий тип "об'єднання" лише з однією функцією конструктора, але цю функцію конструктора можна приховати від інших модулів / пакетів.
Якщо тип експонується так:
module Data exposing (Person)
Тільки код у Data
модулі може створити значення Person, і лише той код може узгоджувати його.
Як я бачу, головна відмінність полягає в тому, чи перевірятиме тип перевірки на вас, якщо ви використовуєте "синомічний" тип.
Створіть наступний файл, покладіть його десь і запустіть elm-reactor
, а потім перейдіть http://localhost:8000
до перегляду різниці:
-- Boilerplate code
module Main exposing (main)
import Html exposing (..)
main =
Html.beginnerProgram
{
model = identity,
view = view,
update = identity
}
-- Our type system
type alias IntRecordAlias = {x : Int}
type IntRecordType =
IntRecordType {x : Int}
inc : {x : Int} -> {x : Int}
inc r = {r | x = .x r + 1}
view model =
let
-- 1. This will work
r : IntRecordAlias
r = {x = 1}
-- 2. However, this won't work
-- r : IntRecordType
-- r = IntRecordType {x = 1}
in
Html.text <| toString <| inc r
Якщо ви 2.
коментуєте та коментуєте, 1.
ви побачите:
The argument to function `inc` is causing a mismatch.
34| inc r
^
Function `inc` is expecting the argument to be:
{ x : Int }
But it is:
IntRecordType
А alias
- це лише коротша назва для іншого типу, подібного class
в OOP. Досвід:
type alias Point =
{ x : Int
, y : Int
}
type
(Без псевдоніма) дозволить вам визначити свій власний тип, так що ви можете визначити типи , як Int
, String
... для вас додатки. Для прикладу, у звичайному випадку, він може використовувати для опису стану програми:
type AppState =
Loading --loading state
|Loaded --load successful
|Error String --Loading error
Тож ви можете легко впоратися з ним у view
в'язі:
-- VIEW
...
case appState of
Loading -> showSpinner
Loaded -> showSuccessData
Error error -> showError
...
Я думаю, ви знаєте різницю між type
і type alias
.
Але чому і як користуватися, type
і що type alias
важливо для elm
програми, ви, хлопці, можете посилатися на статтю Джоша Клейтона