Answers:
我如何看待它:
type
用于定义新的联合类型:
type Thing = Something | SomethingElse
在此定义之前Something
,SomethingElse
并没有任何意义。现在它们都是Thing
我们刚刚定义的type 。
type alias
用于给已经存在的其他类型命名:
type alias Location = { lat:Int, long:Int }
{ lat = 5, long = 10 }
具有type { lat:Int, long:Int }
,它已经是有效的类型。但是现在我们也可以说它具有类型,Location
因为这是同一类型的别名。
值得注意的是,下面的代码将可以很好地编译和显示"thing"
。即使我们指定thing
为a 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
解决了一个不同的问题。如果您来自OOP,那是您通过继承,运算符重载等解决的问题-有时,您希望将数据视为通用的东西,有时希望将其视为特定的东西。
发生这种情况的常见情况是在传递邮件时-例如邮政系统。发送信件时,您希望邮政系统将所有邮件视为同一事物,因此您只需设计一次邮政系统。此外,路由消息的工作应独立于其中包含的消息。只有当信件到达目的地时,您才关心消息是什么。
同样,我们可以将a定义type
为可能发生的所有不同类型消息的并集。假设我们正在实施大学生与父母之间的消息传递系统。因此,大学生只能发送两种消息:“我需要啤酒钱”和“我需要内裤”。
type MessageHome = NeedBeerMoney | NeedUnderpants
因此,现在,当我们设计路由系统时,函数的类型可以传递MessageHome
,而不必担心它可能是所有不同类型的消息。路由系统不在乎。它只需要知道它是一个MessageHome
。只有当邮件到达其目的地即父母的家时,您才需要弄清楚它是什么。
case message of
NeedBeerMoney ->
sayNo()
NeedUnderpants ->
sendUnderpants(3)
如果您了解Elm架构,那么update函数就是一个巨大的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
理查德·费尔德曼(Richard Feldman)在ElmEurope 2017上的演讲可能会为这种风格何时值得使用提供进一步的见解。
重命名内容
您可能会这样做,因为新名称稍后可能会在代码中提供额外的含义,例如本示例
type alias Id = String
type alias ElapsedTime = Time
type SessionStatus
= NotStarted
| Active Id ElapsedTime
| Finished Id
也许在核心中使用这种Time
更好的例子是。
从另一个模块重新公开类型
如果您正在编写一个包(不是应用程序),则可能需要在一个模块中实现一个类型,也许是在一个内部(未公开)模块中实现,但是您想从一个不同的(公共)模块。或者,您也可以公开来自多个模块的类型。
Task
core中的in和Http中的Http.Request是第一个示例,而Json.Encode.Value和Json.Decode.Value对是后面的示例。
只有在其他情况下要保持类型不透明时,才可以执行此操作:不公开构造函数。有关详细信息,请参见type
下面的用法。
值得注意的是,在以上示例中,仅#1提供了构造函数。如果您在#1中公开类型别名module Data exposing (Person)
,则将公开类型名称以及构造函数。
type
定义标记的联合类型
这是最常见的用例,它的一个很好的例子是Maybe
core中的类型:
type Maybe a
= Just a
| Nothing
定义类型时,还定义其构造函数。如果可能的话,这些是(伪代码):
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树算法的实现细节Dict
并直接与节点混淆。隐藏构造函数会强制您的模块/包的使用者通过您公开的函数仅创建您类型的值(然后转换这些值)。
这就是为什么有时这样的东西出现在代码中的原因
type Person =
Person { name : String, age : Int }
与本文type alias
顶部的定义不同,此语法仅使用一个构造函数创建一个新的“联合”类型,但是该构造函数可以从其他模块/包中隐藏。
如果类型是这样暴露的:
module Data exposing (Person)
只有Data
模块中的代码才能创建Person值,并且只有该代码才能在其上进行模式匹配。
正如我所看到的,主要区别在于如果您使用“ 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
An alias
只是其他类型class
的简称,与OOP 类似。经验值:
type alias Point =
{ x : Int
, y : Int
}
一个type
(没有别名)让你定义自己的类型,所以您可以定义类型,比如Int
,String
应用程序,...为您服务。例如,在通常情况下,它可以用于描述应用程序的状态:
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
的应用程序,你们可以参考乔什-克莱顿文章