type
关键字,以及何时适用于 type alias
。文档似乎没有解释这一点,版本发布说明中也找不到相关信息。这个问题有没有被记录在某处?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
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
解决了不同的问题。如果您来自OOP,那么您要解决的问题是继承、运算符重载等。有时,您希望将数据视为通用的东西,有时则希望将其视为特定的东西。type
作为可能发生的所有不同类型的消息的联合。假设我们正在实现一个大学生向父母发送消息的消息系统。因此,只有两种消息可以发送给大学生:'我需要啤酒钱'和'我需要内裤'。type MessageHome = NeedBeerMoney | NeedUnderpants
现在,当我们设计路由系统时,函数的类型只需要传递MessageHome
即可,而不必担心它可能是各种不同类型的消息。路由系统并不关心具体是什么类型,它只需要知道这是一个MessageHome
。只有当消息到达其目的地——父级的主页时,你才需要弄清楚它的具体内容。
case message of
NeedBeerMoney ->
sayNo()
NeedUnderpants ->
sendUnderpants(3)
type alias
的用法 Create an alias and a constructor function for a record
This the most common use-case: you can define an alternate name and constructor function for a particular kind of record format.
type alias Person =
{ name : String
, age : Int
}
Defining the type alias automatically implies the following constructor function (pseudo code):
Person : String -> Int -> { name : String, age : Int }
This can come handy, for instance when you want to write a Json decoder.
personDecoder : Json.Decode.Decoder Person
personDecoder =
Json.Decode.map2 Person
(Json.Decode.field "name" Json.Decode.String)
(Json.Decode.field "age" Int)
Specify required fields
They sometimes call it "extensible records", which can be misleading.
This syntax can be used to specify that you are expecting some record with particular fields present. Such as:
type alias NamedThing x =
{ x
| name : String
}
showName : NamedThing x -> Html msg
showName thing =
Html.text thing.name
Then you can use the above function like this (for example in your view):
let
joe = { name = "Joe", age = 34 }
in
showName joe
Richard Feldman's talk on ElmEurope 2017 may provide some further insight into when this style is worth using.
Renaming stuff
You might do this, because the new names could provide extra meaning
later on in your code, like in this example
type alias Id = String
type alias ElapsedTime = Time
type SessionStatus
= NotStarted
| Active Id ElapsedTime
| Finished Id
Perhaps a better example of this kind of usage in core is Time
.
Re-exposing a type from a different module
If you are writing a package (not an application), you may need to implement a type in one module, perhaps in an internal (not exposed) module, but you want to expose the type from a different (public) module. Or, alternatively, you want to expose your type from multiple modules.
Task
in core and Http.Request in Http are examples for the first, while the Json.Encode.Value and Json.Decode.Value pair is an example of the later.
You can only do this when you otherwise want to keep the type opaque: you don't expose the constructor functions. For details see usages of type
below.
module Data exposing (Person)
,那么将会同时暴露类型名称和构造函数。
type
的用法Define a tagged union type
This is the most common use-case, a good example of it is the Maybe
type in core:
type Maybe a
= Just a
| Nothing
When you define a type, you also define its constructor functions. In case of Maybe these are (pseudo-code):
Just : a -> Maybe a
Nothing : Maybe a
Which means that if you declare this value:
mayHaveANumber : Maybe Int
You can create it by either
mayHaveANumber = Nothing
or
mayHaveANumber = Just 5
The Just
and Nothing
tags not only serve as constructor functions, they also serve as destructors or patterns in a case
expression. Which means that using these patterns you can see inside a Maybe
:
showValue : Maybe Int -> Html msg
showValue mayHaveANumber =
case mayHaveANumber of
Nothing ->
Html.text "N/A"
Just number ->
Html.text (toString number)
You can do this, because the Maybe module is defined like
module Maybe exposing
( Maybe(Just,Nothing)
It could also say
module Maybe exposing
( Maybe(..)
The two are equivalent in this case, but being explicit is considered a virtue in Elm, especially when you are writing a package.
Hiding implementation details
As pointed out above it is a deliberate choice that the constructor functions of Maybe
are visible for other modules.
There are other cases, however, when the author decides to hide them. One example of this in core is Dict
. As the consumer of the package, you should not be able to see the implementation details of the Red/Black tree algorithm behind Dict
and mess with the nodes directly. Hiding the constructor functions forces the consumer of your module/package to only create values of your type (and then transform those values) through the functions you expose.
This is the reason why sometimes stuff like this appears in code
type Person =
Person { name : String, age : Int }
Unlike the type alias
definition at the top of this post, this syntax creates a new "union" type with only one constructor function, but that constructor function can be hidden from other modules/packages.
If the type is exposed like this:
module Data exposing (Person)
Only code in the Data
module can create a Person value and only that code can pattern match on it.
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
别名
只是另一种类型的缩写名称,类似于面向对象编程中的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
之间的区别。
但是为什么以及如何在elm
应用程序中使用type
和type alias
非常重要,你们可以参考Josh Clayton的文章。
{}
记录语法时,您正在定义一个新类型? - user9903{ lat:Int, long:Int }
并不定义一个新的类型,而是一个已经存在的有效类型。type alias Location = { lat:Int, long:Int }
也不会定义新的类型,只是为现有类型提供另一个(或许更加具有描述性的)名称。type Location = Geo { lat:Int, long:Int }
则会定义一个新类型 (Location
)。 - robertjlooby