型を読む

この本の言語の基礎の節では、REPL で一連のコードを実行しました。 さて、私たちはもう一度やってみるつもりですが、今度は表示される型に注目していきましょう。 ターミナルにelm replと入力してください。 このように表示されます:

---- Elm 0.19.0 ----------------------------------------------------------------
Read <https://elm-lang.org/0.19.0/repl> to learn more: exit, help, imports, etc.
--------------------------------------------------------------------------------
>

プリミティブとリスト

いくつかのシンプルな式を入力して、何が起こるかを見てみましょう:

> "hello"
"hello" : String

> not True
False : Bool

> round 3.1415
3 : Int

上の 3 つの例では、REPL は結果として得られる値と、その値のを教えてくれます。 値"hello"Stringです。 値3Intです。 何もおかしなことはありません。

様々な型の値を保持するリストで何が起こるか見てみましょう:

> [ "Alice", "Bob" ]
[ "Alice", "Bob" ] : List String

> [ 1.0, 8.6, 42.1 ]
[ 1.0, 8.6, 42.1 ] : List Float

> []
[] : List a

最初のケースでは、ListにはStringの値が入っています。 2番目のケースでは、ListにはFloatの値が入っています。 3 番目のケースでは、リストは空なので実際にどのような値がリストに入っているかはわかりません。 なので List aという型は、「リストがあるのはわかるが、何の型が入るかはわからない」ということを表現しています。 小文字の a型変数と呼ばれます。つまり、この型変数には特定の型に固定する制約がありません。 言い換えると、その型は使用方法に基づいて変化する可能性があるということです。

関数

関数の型を見てみましょう:

> String.length
<function> : String -> Int

String.length関数はString -> Intという型を持っています。 必ずString型の引数を1つ受け取り、整数の結果を返すことをこの型は意味します。

> String.length "Supercalifragilisticexpialidocious"
34 : Int

なのでString -> Int関数から始めて、Stringを与えてみましょう。 結果はIntが得られます。

String以外を与えたら何が起こるでしょうか?

> String.length [1,2,3]
-- error!

> String.length True
-- error!

String -> Intの関数は必ずString型の値を引数にしなくてはなりません!

Note: 複数の引数を取る関数は、より多くの矢印を持つことになります。 例えば、2つの引数をとる関数はこうなります:

String.repeat : Int -> String -> String

String.repeat 3 "ha"、このように2つ引数を与えると"hahaha"が生成されます。 ->を引数のセパレータとして考えるのは奇妙に思えますが、本当の理由はここで説明しています。 それはとてもすっきりした説明です!

型注釈(タイプアノテーション)

今のところElmに型を推論させているだけですが、必要ならば、定義の上の行に型注釈を書くこともできます。 つまり、次のようにコードを書くことができます:

half : Float -> Float
half n =
  n / 2

-- half 256 == 128
-- half "3" -- error!

hypotenuse : Float -> Float -> Float
hypotenuse a b =
  sqrt (a^2 + b^2)

-- hypotenuse 3 4  == 5
-- hypotenuse 5 12 == 13

checkPower : Int -> String
checkPower powerLevel =
  if powerLevel > 9000 then "It's over 9000!!!" else "Meh"

-- checkPower 9001 == "It's over 9000!!!"
-- checkPower True -- error!

Adding type annotations is not required, but it is definitely recommended! Benefits include:

  1. Error Message Quality — When you add a type annotation, it tells the compiler what you are trying to do. Your implementation may have mistakes, and now the compiler can compare against your stated intent. “You said argument powerLevel was an Int, but it is getting used as a String!”
  2. Documentation — When you revisit code later (or when a colleague visits it for the first time) it can be really helpful to see exactly what is going in and out of the function without having to read the implementation super carefully.

型注釈で間違いを犯す可能性がありますが、実装した内容と一致しない型を書いたらどうなるでしょうか? コンパイラはその実装内で使われている全ての型を推論し、型注釈が実際の型と一致するかどうかをチェックします。 つまり、コンパイラは追加された型注釈が全て正しいことを常に確認します。 それにより、わかりやすいエラーメッセージを出すだけではなく、型が実装の内容を示す ドキュメントとしても 常に最新になっていることを担保できます!

Note: 型注釈を関数定義の上の行に書くのは奇妙だと感じている人もいます。 型注釈を後から追加することが簡単で、関数定義に影響なく書けるようになっていなけれならない、というのが上の行に書く理由です。 このようにして1行追加するだけで、厄介なプロトタイプを高品質のコードに変えることができます。

型変数(タイプバリアブル)

elm/coreの関数を見ると、小文字の型シグネチャがいくつかあることがわかります。 以下のように elm repl で実際に確かめることができます。

訳注: 型シグネチャは関数の引数の型と返り値の型の組み合わせのこと。

> List.length
<function> : List a -> Int

型の中に小文字の a があることに気づきましたか? これは 型変数 と呼ばれるものです。 この a が実際にどんな型になるかは、 [List.length][length] がどのように使われるかによって変わります。

> List.length [1,1,2,3,5,8]
6 : Int

> List.length [ "a", "b", "c" ]
3 : Int

> List.length [ True, False ]
2 : Int

We just want the length, so it does not matter what is in the list. So the type variable a is saying that we can match any type. Let’s look at another common example:

> List.reverse
<function> : List a -> List a

> List.reverse [ "a", "b", "c" ]
["c","b","a"] : List String

> List.reverse [ True, False ]
[False,True] : List Bool

Again, the type variable a can vary depending on how List.reverse is used. But in this case, we have an a in the argument and in the result. This means that if you give a List Int you must get a List Int as well. Once we decide what a is, that’s what it is everywhere.

Note: Type variables must start with a lower-case letter, but they can be full words. We could write the type of List.length as List value -> Int and we could write the type of List.reverse as List element -> List element. It is fine as long as they start with a lower-case letter. Type variables a and b are used by convention in many places, but some type annotations benefit from more specific names.

制約付き型変数

いくつか"制約付き"の型変数があります。 最も一般的な例はおそらくnumber型です。 negate関数はnumberを使用します:

negate : number -> number

通常、型変数は どんな型 でも埋めることができますが、numberIntFloatでのみ埋められます。 制約は可能性を制限します。

制約付き型変数の完全なリストは次の通りです:

  • numberIntFloat で埋められます
  • appendableStringList a で埋められます
  • comparableInt, Float, Char, String, そして comparable な値で構成されるリストまたはタプルで埋められます
  • compappendStringList comparable で埋められます

これらの制約付き型変数は、(+)(<)のような演算子をより柔軟に使えるように存在します。

results matching ""

    No results matching ""