Elm Language
型、型変数、および型コンストラクタ
サーチ…
備考
これらのコンセプトを実際に練習してみてください! elm-repl ( REPL入門を参照)は、おそらく上記のコードを使って遊ぶのに適しています。 elm-replオンラインでもプレイできます 。
同等のデータ型
比較可能な型は、 (<) 、 (>) 、 (<=) 、 (>=) 、 max 、 min 、 compareように、 基本モジュールの比較演算子を使用して比較できるプリミティブ型です。
Elmの比較可能な型は、 Int 、 Float 、 Time 、 Char 、 String 、およびタプルまたは同等の型のリストです。
マニュアルまたはタイプの定義では、彼らは、変数の特殊なタイプと呼ばれているcomparable 、例えば。 Basics.max関数の型定義を参照してください:
max : comparable -> comparable -> comparable
タイプシグネチャ
Elmでは、値は、名前、等号、そして実際の値を書くことによって宣言されます:
someValue = 42
関数も値であり、引数として1つまたは複数の値を取ります。彼らは通常以下のように書かれています:
double n = n * 2
Elmのすべての値には型があります。上記の値のタイプは、使用方法に応じてコンパイラによって推測されます。しかし、常にトップレベルの値の型を明示的に宣言し、以下のように型シグニチャを書くことがベストプラクティスです。
someValue : Int
someValue =
42
someOtherValue : Float
someOtherValue =
42
わかるように、 42はIntまたはFloat いずれかとして定義できます。これは直感的な意味を持ちますが、詳細については変数の型を参照してください。
型シグネチャは、関数と一緒に使用すると特に価値があります。前の倍化関数は次のとおりです。
double : Int -> Int
double n =
n * 2
今回は、シグネチャには-> 、矢印があり、シグネチャを「intからint」、または「整数から整数を返す」と発音します。 ->は、 Int値を引数としてdoubleを与えることにより、 doubleがIntを返すことを示します。したがって、整数に整数が必要です。
> double
<function> : Int -> Int
> double 3
6 : Int
基本タイプ
elm-repl 、コードを入力してその値と推定される型を取得します。存在するさまざまなタイプについて学ぶには、以下を試してください:
> 42
42 : number
> 1.987
1.987 : Float
> 42 / 2
21 : Float
> 42 % 2
0 : Int
> 'e'
'e' : Char
> "e"
"e" : String
> "Hello Friend"
"Hello Friend" : String
> ['w', 'o', 'a', 'h']
['w', 'o', 'a', 'h'] : List Char
> ("hey", 42.42, ['n', 'o'])
("hey", 42.42, ['n', 'o']) : ( String, Float, List Char )
> (1, 2.1, 3, 4.3, 'c')
(1,2.1,3,4.3,'c') : ( number, Float, number', Float, Char )
> {}
{} : {}
> { hey = "Hi", someNumber = 43 }
{ hey = "Hi", someNumber = 43 } : { hey : String, someNumber : number }
> ()
() : ()
{}は空のレコードタイプで、 ()は空のタプルタイプです。後者は、遅延評価の目的でよく使用されます。 関数と部分アプリケーションの対応する例を参照してください。
numberがどのように非大文字で表示されるかに注意してください。これは型変数であることを示しています。さらに、特定の単語numberは、 IntまたはFloat 型の特殊型変数を参照しています(詳細は対応する節を参照してください)。型は常にChar 、 Float 、 List Stringなどの大文字です。
タイプ変数
型変数は、型シグネチャ内の非資本化された名前です。 IntやStringなどの大文字の対応とは異なり、これらは単一のタイプではなく、すべてのタイプを表します。これらは、 任意の型または型で動作できる汎用関数を記述するために使用され、 ListやDictようなコンテナに演算を書く場合に特に便利です。たとえば、 List.reverse関数には次のシグネチャがあります。
reverse : List a -> List a
それはあらゆるタイプの値のリストに取り組むことができることを意味し、そのList Int 、 List (List String) 、これらおよび他のものの両方が可能なreversedすべて同じ。したがって、 aは任意の型を表すことができる型変数です。
reverse関数は、 numberような特殊な型の変数名を除いて、その型シグネチャ内の任意の非大文字の変数名を使用することができました(詳細は対応する例を参照)。
reverse : List lol -> List lol
reverse : List wakaFlaka -> List wakaFlaka
タイプ変数の名前は、単一のシグニチャー内に異なるタイプ変数がある場合にのみ意味を持ちます。例えば、リスト上のmap関数によって例示されます。
map : (a -> b) -> List a -> List b
map 、任意のタイプのいくつかの機能を取るa任意の型にb何らかのタイプの要素を持つリストとともに、 a 、およびいくつかのタイプの要素のリストを返しbがリストの各要素に所定の関数を適用することによって取得し、。
署名をより具体的に見てみましょう:
plusOne : Int -> Int
plusOne x =
x + 1
> List.map plusOne
<function> : List Int -> List Int
我々は、両方見ることができるようにa = Intとb = Intこのケースでは。しかし、もしmapのような型シグネチャたmap : (a -> a) -> List a -> List a 、それが唯一のシングルタイプを操作する関数に働くだろう、とあなたは変更することができませんでしたmap関数を使用してリストの型を指定します。しかし、 mapの型シグネチャには複数の異なる型変数aとbので、 mapを使ってリストの型を変更することができます:
isOdd : Int -> Bool
isOdd x =
x % 2 /= 0
> List.map isOdd
<function> : List Int -> List Bool
この場合、 a = Intおよびb = Boolです。したがって、 異なる型を受け取り、返すことができる関数を使用できるようにするには、異なる型変数を使用する必要があります。
タイプエイリアス
時にはタイプをよりわかりやすい名前にしたい場合もあります。私たちのアプリがユーザーを表すデータ型を持っているとしましょう:
{ name : String, age : Int, email : String }
また、ユーザーの関数には、次の行に沿って型シグネチャがあります。
prettyPrintUser : { name : String, age : Int, email : String } -> String
これは、ユーザーにとってより大きなレコードタイプではかなり扱いにくくなる可能性があるので、 タイプエイリアスを使用してサイズを減らし、そのデータ構造にもっと意味のある名前を付けましょう。
type alias User =
{ name: String
, age : Int
, email : String
}
prettyPrintUser : User -> String
タイプエイリアスを使用すると、アプリケーションのモデルを定義して使用することができます。
type alias Model =
{ count : Int
, lastEditMade : Time
}
type aliasを使用すると、文字通り、そのtype alias名前を付けます。上記のModel型を使用することは、 { count : Int, lastEditMade : Time }を使用する場合とまったく同じです。エイリアスが基になる型と変わらないことを示す例です:
type alias Bugatti = Int
type alias Fugazi = Int
unstoppableForceImmovableObject : Bugatti -> Fugazi -> Int
unstoppableForceImmovableObject bug fug =
bug + fug
> unstoppableForceImmovableObject 09 87
96 : Int
レコード型の型エイリアスは、宣言の順序で各フィールドに対して1つの引数を持つコンストラクタ関数を定義します。
type alias Point = { x : Int, y : Int }
Point 3 7
{ x = 3, y = 7 } : Point
type alias Person = { last : String, middle : String, first : String }
Person "McNameface" "M" "Namey"
{ last = "McNameface", middle = "M", first = "Namey" } : Person
各レコード・タイプ別名は、互換性のあるタイプであっても独自のフィールド順序を持ちます。
type alias Person = { last : String, middle : String, first : String }
type alias Person2 = { first : String, last : String, middle : String }
Person2 "Theodore" "Roosevelt" "-"
{ first = "Theodore", last = "Roosevelt", middle = "-" } : Person2
a = [ Person "Last" "Middle" "First", Person2 "First" "Last" "Middle" ]
[{ last = "Last", middle = "Middle", first = "First" },{ first = "First", last = "Last", middle = "Middle" }] : List Person2
新しいタイプを使用した型安全性の改善
エイリアスタイプはボイラープレートを削減し、読みやすさを向上させますが、エイリアスタイプ自体よりも型保護されていません。次の点を考慮してください。
type alias Email = String
type alias Name = String
someEmail = "[email protected]"
someName = "Benedict"
sendEmail : Email -> Cmd msg
sendEmail email = ...
上記のコードを使用すると、 sendEmail someNameを書くことができます。本当にすべきではありませんが、コンパイルされます。なぜなら、名前と電子メールの両方がStringであるにもかかわらず、
私たちは、本当に1つの区別できるString別のString新しいタイプを作成することにより、タイプレベルで。 type aliasではなくtypeとしてEmailを書き換える例を次に示します。
module Email exposing (Email, create, send)
type Email = EmailAddress String
isValid : String -> Bool
isValid email =
-- ...validation logic
create : String -> Maybe Email
create email =
if isValid email then
Just (EmailAddress email)
else
Nothing
send : Email -> Cmd msg
send (EmailAddress email) = ...
私たちのisValid関数は、文字列が有効な電子メールアドレスかどうかを判断する何かを行います。 create関数は、指定されたStringが有効な電子メールであるかどうかをチェックし、検証済みのアドレスのみを返すように、 Maybe -wrapped Emailを返します。 EmailAddress "somestring"記述してEmail直接作成することで検証チェックを回避できますが、モジュール宣言でEmailAddressコンストラクタが公開されていない場合は、ここに示すように
module Email exposing (Email, create, send)
他のモジュールはEmailAddressコンストラクタにアクセスできませんが、アノテーションでEmailタイプを使用することはできます。このモジュールの外部で新しいEmailを作成する唯一の方法は、提供create関数を使用するcreateであり、その関数は最初に有効な電子メールアドレスを返すことを保証します。したがって、このAPIは型の安全性を介して正しいパスをユーザーに自動的に誘導します。 sendは、 createによって構築された値でのみ動作し、検証を実行し、 Maybe Email返すので無効な電子メールの処理を強制します。
Emailコンストラクタをエクスポートする場合は、次のように記述します。
module Email exposing (Email(EmailAddress), create, send)
Emailをインポートするファイルでも、コンストラクタをインポートできます。この場合、ユーザーは検証を回避しsend無効な電子メールをsendますが、このようなAPIを構築するとは限らないため、コンストラクタをエクスポートすると便利です。いくつかのコンストラクタを持つ型では、それらのうちのいくつかだけをエクスポートしたいかもしれません。
型の構築
type aliasキーワードの組み合わせはtype alias新しい名前を与えますが、 typeキーワードは孤立して新しい型を宣言します。これらのタイプの最も基本的なものの1つを調べてみましょう: Maybe
type Maybe a
= Just a
| Nothing
最初の注意点は、 Maybe型はaの型変数で宣言されているaです。次に注目すべきは、パイプ文字です| 「または」を意味する。言い換えれば、タイプ「 Maybe aは、 Just a または Nothing いずれかです。
あなたは上記のコードを書くときに、 JustとNothing 価値のコンストラクタとしてスコープに来て、 Maybe 型コンストラクタとしてスコープに入ってきます。これらはその署名です:
Just : a -> Maybe a
Nothing : Maybe a
Maybe : a -> Maybe a -- this can only be used in type signatures
型変数 aために、任意の型をMaybe型の "wrapped inside"にすることができます。だから、 Maybe Int 、 Maybe (List String) 、 Maybe (Maybe (List Html))はすべて有効な型です。 case式で任意のtype値を破壊するときは、その型の可能な各インスタンス化を考慮する必要があります。 Maybe a型の値の場合、 Just a caseとNothing caseの両方を考慮する必要があります:
thing : Maybe Int
thing =
Just 3
blah : Int
blah =
case thing of
Just n ->
n
Nothing ->
42
-- blah = 3
case式にNothing句を入れずに上記のコードを書いてみてください:コンパイルされません。これは、 Maybe型コンストラクタを、値がNothingときのロジックを処理するように強制するため、存在しない可能性のある値を表現するための素晴らしいパターンにします。
Never型
Never型は構築できません( Basicsモジュールは値コンストラクタをエクスポートしておらず、 Neverを返す他の関数も与えていません)。 never : Never値はありませnever : Neverまたは関数createNever : ?? -> Never 。
これには利点があります。タイプシステムでは起こり得ない可能性をエンコードできます。これは、 Task Never Intような型では、 Int成功することを保証します。またはProgram Never 、JavaScriptからElmコードを初期化するときにパラメータを使用しません。
特殊型変数
Elmは、コンパイラにとって特別な意味を持つ以下の特殊型変数を定義します。
comparable:Int、Float、Char、String、およびそのタプルで構成されます。これにより、<および>演算子の使用が可能になります。例:リスト(
extent)内の最小要素と最大要素を検索する関数を定義できます。あなたはどのタイプの署名を書くのか考えています。一方では、extentInt : List Int -> Maybe (Int, Int)とextentChar : List Char -> Maybe (Char, Char)とFloatとString別のものを書くことができます。これらの実装は同じです:extentInt list = let helper x (minimum, maximum) = ((min minimum x), (max maximum x)) in case list of [] -> Nothing x :: xs -> Just <| List.foldr helper (x, x) xsextent : List a -> Maybe (a, a)書くことに誘惑されるかもしれませんextent : List a -> Maybe (a, a)ですが、関数minとmaxはこれらの型に対して定義されていないので、コンパイラはこれを許可しません(注意:上記の<演算子のまわり)。extent : List comparable -> Maybe (comparable, comparable)を定義することでこれを解決できextent : List comparable -> Maybe (comparable, comparable)これにより、ソリューションが多形的になることができます。これは、複数の型に対して機能することを意味します。number:IntとFloatます。除算以外の算術演算子の使用を許可します。たとえば、sum : List number -> numberを定義し、整数と浮動小数点の両方で動作させることができます。appendable:String、Listます。++演算子の使用を許可します。compappend:これは時々現れますが、コンパイラの実装の詳細です。現在のところ、これは自分のプログラムでは使用できませんが、時には言及されることもあります。
次のようなタイプの注釈では、 number -> number -> numberのすべてが同じ型を参照するため、 Int -> Float -> Intを渡すと型エラーになります。これを解決するには、型変数名に接尾辞を追加します: number -> number' -> number''は、コンパイルがうまくいきます。
これらの正式な名前はありません、彼らは時々呼ばれます:
- 特殊型変数
- Typeclassのような型変数
- 疑似型
これはHaskellの型クラスのように動作するが、ユーザーがこれらを定義する能力がないためである。