В Elm я не могу понять, когда type
подходит ключевое слово или type alias
. Похоже, что в документации нет объяснения этого, и я не могу найти его в примечаниях к выпуску. Это где-то задокументировано?
Ответы:
Как я об этом думаю:
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
{ lat:Int, long:Int }
не определяет новый тип. Это уже действительный тип. type alias Location = { lat:Int, long:Int }
также не определяет новый тип, а просто дает другое (возможно, более описательное) имя уже действующему типу. type Location = Geo { lat:Int, long:Int }
определит новый тип ( Location
)
Ключ - это слово 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, чтобы точно определить, что это было за сообщение, чтобы мы могли с ним справиться.
Позвольте мне дополнить предыдущие ответы, сосредоточив внимание на вариантах использования и предоставив небольшой контекст для функций и модулей конструкторов.
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
. Как потребитель пакета, вы не должны видеть детали реализации алгоритма красного / черного дерева Dict
и напрямую связываться с узлами. Скрытие функций конструктора заставляет потребителя вашего модуля / пакета создавать только значения вашего типа (а затем преобразовывать эти значения) с помощью функций, которые вы предоставляете.
Это причина, по которой иногда в коде появляются подобные вещи.
type Person =
Person { name : String, age : Int }
В отличие от type alias
определения в верхней части этого сообщения, этот синтаксис создает новый тип «объединения» только с одной функцией-конструктором, но эту функцию-конструктор можно скрыть от других модулей / пакетов.
Если тип отображается следующим образом:
module Data exposing (Person)
Только код в Data
модуле может создать значение Person, и только этот код может сопоставить его с шаблоном.
Основное различие, на мой взгляд, в том, будет ли на вас кричать средство проверки типов, если вы используете «синомический» тип.
Создайте следующий файл, поместите его куда-нибудь и запустите 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
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