В 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
-
Создать псевдоним и функцию конструктора для записи
Это наиболее распространенный вариант использования: вы можете определить альтернативное имя и функцию конструктора для определенного типа формата записи.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 предоставляет функцию конструктора. Если вы выставите свой псевдоним типа в # 1, например, в module Data exposing (Person)
, который представит как имя типа, так и функцию конструктора.
Использование type
-
Определить помеченный тип объединения
Это наиболее распространенный вариант использования, хорошим примером которого является тип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 достоинством, особенно когда вы пишете пакет.
-
Сокрытие деталей реализации
Как указывалось выше, это преднамеренный выбор, чтобы функции конструктора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
, вы, ребята, можете сослаться на статью Джоша Клейтона