Різниця в Elm між псевдонімом типу і типу?


93

В Elm, я не можу зрозуміти, коли typeце доречно ключове слово проти type alias. Схоже, документація не пояснює цього, і я не можу знайти його у примітках до випуску. Це десь задокументовано?

Відповіді:


136

Як я думаю про це:

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". Незважаючи на те, що ми вказуємо " thinga" 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

Не впевнений у своєму пункті останнього абзацу. Ви намагаєтесь сказати, що вони все одно того ж типу, незалежно від того, як ви його псевдонімом?
ZHANG Cheng

7
Так, лише зазначивши, що компілятор вважає тип псевдоніму таким же, як і оригінал
robertjlooby

Отже, коли ви використовуєте {}синтаксис запису, ви визначаєте новий тип?

2
{ lat:Int, long:Int }не визначає нового типу. Це вже дійсний тип. type alias Location = { lat:Int, long:Int }також не визначає новий тип, він просто дає інше (можливо, більш описове) ім'я вже дійсному типу. type Location = Geo { lat:Int, long:Int }визначив би новий тип ( Location)
robertjlooby

1
Коли слід використовувати псевдонім типу типу? Де мінус завжди використовувати тип?
Річард Хейвен

8

Ключ - це слово 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, функція оновлення - це гігантський випадок, оскільки це місце, куди повідомлення надсилається, а отже, обробляється. І ми використовуємо типи об'єднань, щоб мати єдиний тип для вирішення при передачі повідомлення навколо, але потім можемо використовувати випадок випадку, щоб перевірити, яке саме повідомлення було, тому ми можемо з ним розібратися.


5

Дозвольте доповнити попередні відповіді, зосереджуючись на випадках використання та надаючи невеликий контекст функціям і модулям конструктора.



Використання type alias

  1. Створення псевдоніма та функції конструктора для запису
    Це найпоширеніший варіант використання: ви можете визначити альтернативне ім'я та функцію конструктора для певного виду формату запису.

    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)


  2. Вкажіть необхідні поля.
    Іноді вони називають це "розширювані записи", що може ввести в оману. Цей синтаксис можна використовувати, щоб вказати, що ви очікуєте деякий запис із наявними певними полями. Як от:

    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 може дати деяке подальше розуміння того, коли цей стиль варто використовувати.

  3. Перейменування речей
    Ви можете зробити це, тому що нові імена можуть надавати додаткове значення у вашому коді, як у цьому прикладі

    type alias Id = String
    
    type alias ElapsedTime = Time
    
    type SessionStatus
        = NotStarted
        | Active Id ElapsedTime
        | Finished Id

    Можливо, кращим прикладом такого використання в ядрі єTime .

  4. Повторне опромінення типу з іншого модуля
    Якщо ви пишете пакет (а не додаток), можливо, вам доведеться реалізувати тип в одному модулі, можливо, у внутрішньому (не піддається впливу) модулі, але ви хочете викрити тип з інший (загальнодоступний) модуль. Або, як альтернатива, ви хочете викрити свій тип з декількох модулів.
    Taskв ядрі та Http.Request в Http - приклади першого, тоді як пара Json.Encode.Value і Json.Decode.Value є прикладом пізнішого.

    Ви можете це зробити лише тоді, коли в іншому випадку хочете зберегти тип непрозорим: ви не піддаєте функції конструктора. Детальніше див. Звичаї typeнижче.

Варто зазначити, що у наведених вище прикладах лише №1 забезпечує функцію конструктора. Якщо ви виставите псевдонім свого типу в module Data exposing (Person)такому №1 , це відкриє ім'я типу, а також функцію конструктора.



Використання type

  1. Визначте теговий тип об'єднання
    Це найпоширеніший випадок використання, хорошим прикладом цього є 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, особливо коли ви пишете пакет.


  1. Приховування деталей реалізації
    Як було зазначено вище, це навмисний вибір, який функції конструктора Maybeвидимі для інших модулів.

    Однак є й інші випадки, коли автор вирішує їх приховати. Одним із прикладів цього в основі єDict . Будучи споживачем пакету, ви не повинні бачити деталі реалізації алгоритму Red / Black tree позаду Dictта безладно зв’язуватися з вузлами. Приховування функцій конструктора змушує споживача вашого модуля / пакета створювати лише значення вашого типу (а потім трансформувати ці значення) за допомогою функцій, які ви відкриваєте.

    З цієї причини іноді такі речі з'являються в коді

    type Person =
        Person { name : String, age : Int }

    На відміну від type alias визначення вгорі цієї публікації, цей синтаксис створює новий тип "об'єднання" лише з однією функцією конструктора, але цю функцію конструктора можна приховати від інших модулів / пакетів.

    Якщо тип експонується так:

    module Data exposing (Person)

    Тільки код у Dataмодулі може створити значення Person, і лише той код може узгоджувати його.


1

Як я бачу, головна відмінність полягає в тому, чи перевірятиме тип перевірки на вас, якщо ви використовуєте "синомічний" тип.

Створіть наступний файл, покладіть його десь і запустіть 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

0

А 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програми, ви, хлопці, можете посилатися на статтю Джоша Клейтона

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.