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

В Elm я не могу понять, когда type - соответствующее ключевое слово vs type alias. В документации, похоже, нет объяснений, и я не могу найти ее в примечаниях к выпуску. Является ли это документированным где-нибудь?

Ответ 1

Как я об этом думаю:

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, это 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

Ответ 2

Ключом является слово 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 решает другую проблему. Если вы пришли из ООП, это проблема, которую вы решаете с наследованием, перегрузкой оператора и т.д. - иногда вы хотите обрабатывать данные как общую вещь, а иногда вы хотите относиться к ней как к определенной вещи.

Обычное явление, когда это происходит, - это передача сообщений - как почтовая система. Когда вы отправляете письмо, вы хотите, чтобы почтовая система обрабатывала все сообщения как одно и то же, поэтому вам нужно только один раз создать почтовую систему. И кроме того, задача маршрутизации сообщения должна быть независимой от сообщения, содержащегося внутри. Это только, когда письмо доходит до места назначения, вы заботитесь о том, что такое сообщение.

Таким же образом мы могли бы определить a type как объединение всех различных типов сообщений, которые могут произойти. Скажем, мы внедряем систему обмена сообщениями между студентами колледжей с родителями. Таким образом, есть только два сообщения, которые могут отправить дети колледжа: "Мне нужны пивные деньги" и "Мне нужны трусы".

type MessageHome = NeedBeerMoney | NeedUnderpants

Итак, когда мы проектируем систему маршрутизации, типы для наших функций могут просто проходить MessageHome, а не беспокоиться обо всех различных типах сообщений, которые могут быть. Система маршрутизации не волнует. Это нужно знать только MessageHome. Это только тогда, когда сообщение достигнет своего адресата, родительского дома, что вам нужно выяснить, что это такое.

case message of
  NeedBeerMoney ->
    sayNo()
  NeedUnderpants ->
    sendUnderpants(3)

Если вы знаете архитектуру Elm, функция обновления является гигантским аргументом case, потому что это место назначения, где сообщение маршрутизируется и, следовательно, обрабатывается. И мы используем типы union, чтобы иметь один тип, с которым приходится иметь дело при передаче сообщения, но затем он может использовать оператор case, чтобы дразнить именно то, что это было, поэтому мы можем справиться с ним.

Ответ 3

Позвольте мне дополнить предыдущие ответы, сосредоточившись на сценариях использования и предоставив небольшой контекст о функциях и модулях конструктора.



Использование 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 предоставляет функцию конструктора. Если вы выставите свой псевдоним типа в # 1, например, в module Data exposing (Person), который представит как имя типа, так и функцию конструктора.



Использование type

  1. Определить помеченный тип объединения
    Это наиболее распространенный вариант использования, хорошим примером которого является тип Maybe в ядре:

    type Maybe a
        = Just a
        | Nothing
    

    Когда вы определяете тип, вы также определяете его функции конструктора. В случае Maybe это (псевдокод):

    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)
    

    Вы можете сделать это, потому что модуль Maybe определен как

    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 в начале этого поста, этот синтаксис создает новый тип "union" только с одной функцией конструктора, но эту функцию конструктора можно скрыть от других модулей/пакетов.

    Если тип выставлен так:

    module Data exposing (Person)
    

    Только код в модуле Data может создать значение Person, и только этот код может соответствовать шаблону.

Ответ 4

Основное различие, как я вижу, заключается в том, что на вас будет кричать тип, если вы используете "синонимный" тип.

Создайте следующий файл, поместите его где-нибудь и запустите 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

Ответ 5

alias - это просто более короткое имя для некоторого другого типа, похожего на class в ООП. Опыт:

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, вы, ребята, можете сослаться на статью Джоша Клейтона