类型和类型别名之间的Elm差异?


93

在榆树,当我想不通type的是适当的关键字对type alias。该文档似乎没有对此的解释,我也无法在发行说明中找到它。这是在某处记录的吗?

Answers:


136

我如何看待它:

type 用于定义新的联合类型:

type Thing = Something | SomethingElse

在此定义之前SomethingSomethingElse并没有任何意义。现在它们都是Thing我们刚刚定义的type 。

type alias 用于给已经存在的其他类型命名:

type alias Location = { lat:Int, long:Int }

{ lat = 5, long = 10 }具有type { lat:Int, long:Int },它已经是有效的类型。但是现在我们也可以说它具有类型,Location因为这是同一类型的别名。

值得注意的是,下面的代码将可以很好地编译和显示"thing"。即使我们指定thing为a StringaliasedStringIdentity采用一个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

不确定您对最后一段的看法。您是否要说不管您如何给它们起别名,它们仍然是相同的类型?
张成

7
是的,只是指出编译器认为别名类型与原始类型相同
robertjlooby 16-4-4

因此,当您使用{}记录语法时,您是在定义新类型吗?

2
{ lat:Int, long:Int }没有定义新类型。那已经是有效的类型了。type alias Location = { lat:Int, long:Int }也没有定义新的类型,它只是为已经有效的类型提供了另一个(也许更具描述性)名称。type Location = Geo { lat:Int, long:Int }将定义一个新类型(Location
robertjlooby

1
什么时候应该使用类型vs类型别名?始终使用类型的缺点在哪里?
理查德·黑文

8

关键是单词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语句来弄清楚它到底是什么消息,因此我们可以对其进行处理。


5

让我通过关注用例并在构造函数和模块上提供一些上下文来补充先前的答案。



的用法 type alias

  1. 为记录创建别名和构造函数
    这是最常见的用例:您可以为特定类型的记录格式定义备用名称和构造函数。

    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)
    


  2. 指定必填字段
    他们有时称其为“可扩展记录”,这可能会引起误解。此语法可用于指定您期望某些记录包含特定字段。如:

    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上的演讲可能会为这种风格何时值得使用提供进一步的见解。

  3. 重命名内容
    您可能会这样做,因为新名称稍后可能会在代码中提供额外的含义,例如本示例

    type alias Id = String
    
    type alias ElapsedTime = Time
    
    type SessionStatus
        = NotStarted
        | Active Id ElapsedTime
        | Finished Id
    

    也许在核心中使用这种Time更好的例子

  4. 从另一个模块重新公开类型
    如果您正在编写一个包(不是应用程序),则可能需要在一个模块中实现一个类型,也许是在一个内部(未公开)模块中实现,但是您想从一个不同的(公共)模块。或者,您也可以公开来自多个模块的类型。
    Taskcore中的in和Http中的Http.Request是第一个示例,而Json.Encode.ValueJson.Decode.Value对是后面的示例。

    只有在其他情况下要保持类型不透明时,才可以执行此操作:不公开构造函数。有关详细信息,请参见type下面的用法。

值得注意的是,在以上示例中,仅#1提供了构造函数。如果您在#1中公开类型别名module Data exposing (Person),则将公开类型名称以及构造函数。



的用法 type

  1. 定义标记的联合类型
    这是最常见的用例,它的一个很好的例子是Maybecore中类型

    type Maybe a
        = Just a
        | Nothing
    

    定义类型时,还定义其构造函数。如果可能的话,这些是(伪代码):

    Just : a -> Maybe a
    
    Nothing : Maybe a
    

    这意味着如果您声明此值:

    mayHaveANumber : Maybe Int

    您可以通过以下任一方式创建它

    mayHaveANumber = Nothing

    要么

    mayHaveANumber = Just 5

    JustNothing标签不仅可以作为构造函数,它们也可以作为在析构函数或图案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中将其明确视为一种优点,尤其是在编写程序包时。


  1. 隐藏实现细节
    如上所指出,这是一个故意选择的构造函数Maybe对于其他模块可见的选择。

    但是,在其他情况下,作者决定隐藏它们。核心的一个示例是Dict。作为程序包的使用者,您应该看不到后面的Red / Black树算法的实现细节Dict并直接与节点混淆。隐藏构造函数会强制您的模块/包的使用者通过您公开的函数仅创建您类型的值(然后转换这些值)。

    这就是为什么有时这样的东西出现在代码中的原因

    type Person =
        Person { name : String, age : Int }
    

    与本文type alias顶部的定义不同,此语法仅使用一个构造函数创建一个新的“联合”类型,但是该构造函数可以从其他模块/包中隐藏。

    如果类型是这样暴露的:

    module Data exposing (Person)

    只有Data模块中的代码才能创建Person值,并且只有该代码才能在其上进行模式匹配。


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

0

An alias只是其他类型class的简称,与OOP 类似。经验值:

type alias Point =
  { x : Int
  , y : Int
  }

一个type(没有别名)让你定义自己的类型,所以您可以定义类型,比如IntString应用程序,...为您服务。例如,在通常情况下,它可以用于描述应用程序的状态:

type AppState = 
  Loading          --loading state
  |Loaded          --load successful
  |Error String    --Loading error

因此,您可以在viewelm中轻松处理它:

-- VIEW
...
case appState of
    Loading -> showSpinner
    Loaded -> showSuccessData
    Error error -> showError

...

我想你知道之间的区别typetype alias

但是,为什么以及如何使用type,并type alias与重要elm的应用程序,你们可以参考乔什-克莱顿文章

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.