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