在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
关键是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)
如果您了解Elm体系结构,则更新函数是一个巨大的case语句,因为它是消息路由的目的地,因此被处理。我们在传递消息时使用union类型来处理一个类型,但是然后可以使用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
Richard Feldman's talk on ElmEurope 2017可以进一步了解这种风格何时值得使用。type alias Id = String
type alias ElapsedTime = Time
type SessionStatus
= NotStarted
| Active Id ElapsedTime
| Finished Id
也许是this kind of usage in core is Time
的一个更好的例子。Task
的Http.Request in Http是第一个的例子,而Json.Encode.Value和Json.Decode.Value对是后者的一个例子。
只有在您希望保持类型不透明时才能执行此操作:您不公开构造函数。有关详细信息,请参阅下面的type
用法。值得注意的是,在上面的例子中,只有#1提供了构造函数。如果在#1中公开类型别名,如module Data exposing (Person)
,它将公开类型名称以及构造函数。
type
的用法Maybe
type in core:
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
的构造函数对于其他模块是可见的是一个有意识的选择。
但是,当作者决定隐藏它们时,还有其他情况。 One example of this in core is 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
alias
只是其他类型的一个较短的名字,类似于OOP中的class
。经验:
type alias Point =
{ x : Int
, y : Int
}
一个type
(没有别名)将允许您定义自己的类型,因此您可以为您的app定义类型Int
,String
,....例如,在常见情况下,它可以用于描述app的状态:
type AppState =
Loading --loading state
|Loaded --load successful
|Error String --Loading error
所以你可以在view
elm中轻松处理它:
-- VIEW
...
case appState of
Loading -> showSpinner
Loaded -> showSuccessData
Error error -> showError
...
我想你知道type
和type alias
之间的区别。
但是为什么以及如何使用type
和type alias
对elm
app很重要,你们可以参考article from Josh Clayton