web-dev-qa-db-ja.com

タイプとタイプエイリアスのエルムの違いは?

エルムでは、typeが適切なキーワードとtype alias。ドキュメントにはこれについての説明がないようです。また、リリースノートで見つけることもできません。これはどこかに文書化されていますか?

89
ehdv

私の考え:

typeは、新しい共用体タイプの定義に使用されます。

type Thing = Something | SomethingElse

この定義の前は、SomethingSomethingElseは何の意味もありませんでした。現在、これらは両方ともタイプThingであり、先ほど定義しました。

type aliasは、既に存在する他のタイプに名前を付けるために使用されます。

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

{ lat = 5, long = 10 }のタイプは{ lat:Int, long:Int }、これは既に有効なタイプでした。しかし今では、Location型を持っていると言うこともできます。これは、同じ型のエイリアスだからです。

次のコードが正常にコンパイルされ、"thing"thingStringであり、aliasedStringIdentityAliasedStringであることを指定しても、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
132
robertjlooby

キーはWord 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ステートメントを使用して、メッセージが何であったかを正確に引き出すことができるため、対処できます。

8
Wilhelm

ユースケースに焦点を当て、コンストラクター関数とモジュールに関する小さなコンテキストを提供することにより、以前の回答を補完させてください。



_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でのリチャードフェルドマンの講演 は、このスタイルがいつ使用する価値があるかについてのさらなる洞察を提供するかもしれません。

  3. ものの名前を変更する
    これは、この例のように、コードの後半で新しい名前が追加の意味を提供する可能性があるためです。

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

    おそらく コアでのこの種の使用法はTime のより良い例でしょう。

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

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

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



typeの使用法

  1. タグ付きユニオン型を定義する
    これは最も一般的な使用例です。その良い例は、 Maybeコアのタイプ :です。

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

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


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

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

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

    _type Person =
        Person { name : String, age : Int }
    _

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

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

    _module Data exposing (Person)
    _

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

4
Gabor

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

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

aliasは、OOPのclassに似た他のタイプの単なる短い名前です。経験:

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

type(エイリアスなし)を使用すると、独自の型を定義できるため、アプリ用にIntString、...などの型を定義できます。例として、一般的な場合、アプリの状態の説明に使用できます。

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

...

typetype aliasの違いを知っていると思います。

しかし、typetype aliasを使用する理由と方法はElmアプリで重要です。皆さんは Josh Claytonの記事

0
hien