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

93

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

ehdv
источник

Ответы:

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". Несмотря на то, что мы указываем 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
Робертжлуби
источник
Не уверен в том, что вы сказали в последнем абзаце. Вы хотите сказать, что они все того же типа, независимо от того, как вы его называете?
ZHANG Cheng
8
Да, просто отмечу, что компилятор считает, что псевдонимный тип такой же, как и оригинал
robertjlooby
Итак, когда вы используете {}синтаксис записи, вы определяете новый тип?
3
{ 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решает другую проблему. Если вы пришли из ООП, это проблема, которую вы решаете с помощью наследования, перегрузки операторов и т. Д. - иногда вы хотите рассматривать данные как нечто общее, а иногда вы хотите рассматривать их как конкретную вещь.

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

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

type MessageHome = NeedBeerMoney | NeedUnderpants

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

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

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

Вильгельм
источник
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 предоставляет функцию конструктора. Если вы укажете псевдоним типа в # 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 . Как потребитель пакета, вы не должны видеть детали реализации алгоритма красного / черного дерева 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
EugZol
источник
0

An 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приложением, вы , ребята , можете передать статью Джош Clayton

привет
источник