タイプとタイプエイリアスのElmの違い?


93

Elmではtype、適切なキーワードとの比較がわかりませんtype alias。ドキュメンテーションにはこれの説明がないようで、リリースノートにも説明がありません。これはどこかに文書化されていますか?

回答:


136

私はそれをどう思いますか:

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"thingis を指定してStringaliasedStringIdentity受け取りますが、/のAliasedString間に型の不一致があるというエラーは発生しません。StringAliasedString

import Graphics.Element exposing (show)

type alias AliasedString = String

aliasedStringIdentity: AliasedString -> AliasedString
aliasedStringIdentity s = s

thing : String
thing = "thing"

main =
  show <| aliasedStringIdentity thing

最後の段落のポイントがわからない。どのようにエイリアスしても、それらはまだ同じタイプであると言いたいのですか?
ZHANG Cheng

7
はい、コンパイラがエイリアスされた型を元の型と同じであると見なしていることを指摘するだけです
robertjlooby

{}レコード構文を使用すると、新しいタイプを定義することになりますか?

2
{ lat:Int, long:Int }新しいタイプを定義しません。これはすでに有効なタイプです。type alias Location = { lat:Int, long:Int }また、新しいタイプを定義せず、すでに有効なタイプに別の(おそらくより説明的な)名前を付けます。type Location = Geo { lat:Int, long:Int }新しいタイプ(Location)を定義します
robertjlooby

1
タイプエイリアスとタイプエイリアスのどちらを使用すればよいですか?常に型を使用することの欠点はどこですか?
Richard Haven

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を使用している場合は、継承や演算子のオーバーロードなどで解決する問題です。データを一般的なものとして扱いたい場合もあれば、特定のもののように扱いたい場合もあります。

これが発生する一般的な場所は、郵便システムのようにメッセージを渡すときです。手紙を送るとき、郵便システムがすべてのメッセージを同じものとして扱うようにしたいので、郵便システムを設計する必要があるのは一度だけです。さらに、メッセージをルーティングするジョブは、その中に含まれるメッセージとは独立している必要があります。手紙が目的地に到着したときにのみ、メッセージが何であるかを気にします。

同様に、発生する可能性のtypeあるすべてのタイプのメッセージの結合としてを定義する場合があります。大学生とその親の間でメッセージングシステムを実装しているとします。したがって、大学生が送信できるメッセージは、「ビールのお金が必要」と「パンツが必要」の2つだけです。

type MessageHome = NeedBeerMoney | NeedUnderpants

したがって、ルーティングシステムを設計するとき、関数のMessageHomeタイプは、メッセージのすべての異なるタイプについて心配するのではなく、単に渡すことができます。ルーティングシステムは関係ありません。それがであることを知る必要があるだけMessageHomeです。それが何であるかを理解する必要があるのは、メッセージがその宛先である親の家に到達したときだけです。

case message of
  NeedBeerMoney ->
    sayNo()
  NeedUnderpants ->
    sendUnderpants(3)

Elmアーキテクチャを知っている場合、更新関数は巨大な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

    ElmEurope 2017に関するRichard Feldmanの講演は、このスタイルがいつ使用する価値があるかについてのさらなる洞察を提供するかもしれません。

  3. ものの名前の変更
    、新しい名前は、この例のように、後であなたのコード内の余分な意味を提供することができますので、あなたがこれを行う可能性がありますが

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

    おそらく、コアTimeでのこの種の使用のより良い例です。

  4. 別のモジュールから型を再公開する
    (アプリケーションではなく)パッケージを作成している場合、1つのモジュール、おそらく内部(非公開)モジュールに型を実装する必要があるかもしれませんが、別の(パブリック)モジュール。または、代わりに、複数のモジュールから型を公開する必要があります。最初の例
    TaskはコアのHttp.Requestで後者の例はJson.Encode.ValueJson.Decode.Valueのペアです。

    型を不透明にしたい場合にのみ、これを行うことができます。コンストラクター関数を公開しないでください。詳細については、type以下の使用法を参照してください。

上記の例では#1のみがコンストラクター関数を提供していることに注意してください。#1でタイプエイリアスmodule Data exposing (Person)を公​​開すると、タイプ名とコンストラクター関数が公開されます。



の使用法 type

  1. タグ付き共用体タイプを定義する
    これは最も一般的なユースケースでありMaybe、コアタイプがその良い例です。

    type Maybe a
        = Just a
        | Nothing

    タイプを定義するときは、そのコンストラクター関数も定義します。多分これらは(疑似コード)です:

    Just : a -> Maybe a
    
    Nothing : Maybe a

    つまり、この値を宣言すると、

    mayHaveANumber : Maybe Int

    どちらでも作成できます

    mayHaveANumber = Nothing

    または

    mayHaveANumber = Just 5

    タグだけでなく、彼らはまた、デストラクタやパターンとして機能し、コンストラクター関数として機能表現。つまり、これらのパターンを使用すると、内部で確認できます。JustNothingcaseMaybe

    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(..)

    この場合、2つは同等ですが、特にパッケージを作成している場合、Elmでは明示的であることは美徳と見なされます。


  1. 実装の詳細を非表示に
    する上記で指摘したように、コンストラクター関数がMaybe他のモジュールから見えるようにするのは意図的な選択です。

    ただし、作成者が非表示にすることを決定する場合もあります。コアでのこの1つの例はDictです。パッケージのコンシューマーとして、背後にあるRed / Blackツリーアルゴリズムの実装の詳細を確認しDictたり、ノードを直接操作したりすることはできません。コンストラクター関数を非表示にすると、モジュール/パッケージのコンシューマーは、公開する関数を介して型の値のみを作成(そしてそれらの値を変換)します。

    これが時々このようなものがコードに現れる理由です

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

    type aliasこの投稿の冒頭の定義とは異なり、この構文はコンストラクター関数を1つだけ持つ新しい「共用体」型を作成しますが、そのコンストラクター関数は他のモジュール/パッケージから隠すことができます。

    タイプが次のように公開されている場合:

    module Data exposing (Person)

    Dataモジュール内のコードのみがPerson値を作成でき、そのコードのみがそれにパターンマッチできます。


1

私が見るように、主な違いは、「類義的な」タイプを使用した場合にタイプチェッカーがあなたに怒鳴るかどうかです。

次のファイルを作成し、どこかに配置して実行し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

アンは、alias類似した他のいくつかのタイプ、のためだけの短い名前ですclassOOPインチ 経験:

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

...

私はあなたが違いを知っていると思うtypeと、をtype alias

しかし、なぜ、どのように使用しtype、アプリでtype alias重要なのか、Josh Claytonの記事をelm参照してください。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.