在Elm中,我无法确定type
何时是适当的关键字与type alias
。文档似乎没有对此进行解释,也无法在发行说明中找到。这是在某处记录的吗?
答案 0 :(得分:131)
我怎么想呢:
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
,我们也不会收到{{1}之间存在类型不匹配的错误} / String
:
AliasedString
答案 1 :(得分:7)
关键是单词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语句来确切地说明它是什么消息,所以我们可以处理它。
答案 2 :(得分:4)
让我通过关注用例并在构造函数和模块上提供一些上下文来补充以前的答案。
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(..)
在这种情况下,这两者是等价的,但明确被认为是榆树的一种美德,特别是在你写一个包时。
隐藏实施细节
如上所述,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值,只有该代码可以在其上进行模式匹配。
答案 3 :(得分:1)
正如我所看到的,主要区别在于,如果您使用" synomical"那么类型检查器是否会对您大喊大叫。类型。
创建以下文件,将其放在某处并运行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
答案 4 :(得分:0)
alias
只是其他某些类型的简称,类似于OOP中的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
很重要,你们可以参考article from Josh Clayton