NaClの野口です。
最近Haskellを勉強し始めました。
勉強を始めてまだ日が浅いですが、面白い概念を多く学ぶことが出来たと感じています。
本記事ではHaskellの型について自分が理解したことを簡単にまとめます。
Haskellを動かす環境の構築についても記述しますので、読者の方も実際にコードを動かしてみてください。
本記事内ではGHCiという対話形式でHaskellのコードを実行できるツールを使用します。
また実行環境のOSはUbuntuであることを前提として記載しています。
ではさっそく以下のコマンドを実行してGHCiをインストールしましょう。
Ubuntu以外のOSを使用している場合は、ドキュメントを参照してください。
上記コマンドを実行すると様々なツールがインストールされますが、本記事内ではGHCiのみ使用します。
HaskellのコードをGHCiで実行する方法について記載します。
まず以下のサンプルコードをtest.hs
という名前で作業用ディレクトリに保存してください。
後で詳しく解説しますが、上記コードでは引数2つを加算する関数(add2)の定義を行っています。
次にサンプルコードを保存したディレクトリ内で以下のコマンドを実行し、GHCiを起動します。
GHCiの起動に成功した場合は、プロンプト(Prelude>
)が表示されて入力待ちの状態になります。
ではサンプルコードをGHCiにロードしてみましょう。
:load
のように「:」が付いているものはGHCiのコマンドです。
上記のようなメッセージが表示され、プロンプト(*Main>
)が表示されればロード成功です。
<no location info>: can't find file: ./test.hs
のようなメッセージが表示された場合は、
GHCiを起動したディレクトリ内にサンプルコードが存在することをもう一度確認してください。
サンプルコードのロードに成功後、関数(add2)が実行できることを確認します。
関数の引数は カンマで区切る必要がない ことに注意してください。
関数に2つの引数を渡して実行すると、それらを加算した値が返ってくることが確認できました。
まずHaskellには様々な型があります。
例えば整数を表すInt型、文字を表すChar型、文字列を表すString型などです。
他にも知りたい方は基本的なデータ型 - ウォークスルー Haskellを参照してください。
では関数の型宣言についての説明に移ります。
関数の型宣言とは「関数の引数と戻り値がどのような型であるかを宣言する」ことです。
先程実行したサンプルコードで関数の型宣言を行っている箇所を以下に示します。
日本語で説明すると、「add2はInt型の値を2つ入力し、Int型の値を1つ出力する関数」となります。
最後の型が戻り値、それ以外は引数の型を順番に記述していると考えてください。
つまり引数が3つになった場合の関数(add3)の型宣言は以下のようになるということです。
またGHCiには関数の型を調べるための便利なコマンドがあります。
型変数、型クラスという概念について説明する前に、関数(add2)の挙動を詳しく見てみます。
引数に整数を渡したときは問題なく動いています。
では引数に小数を渡してみましょう。
引数に小数(2.0)を渡したことが原因でエラーが発生しました。
このままでは小数の足し算が出来ないので修正したいと思います。
まずInt型を小数を許容する型(Float型)に変更する方法から試してみましょう。
GHCiに変更をロードして確認してみます。
問題なく計算できていますね。
では2つの引数が整数の場合はどうなるのでしょうか。
結果が小数になってしまいましたね。
2つの引数が整数の場合は整数が戻り値になって欲しいです。
上記を解決するためには型変数という概念を使用します。
型変数について詳しい説明をする前に、型変数を使ったバージョンのコード示します。
上記コードのaが 型変数 です。
サンプルでは型変数名にaを使っていますが、b, cや複数文字を使っても問題ありません。
この型変数は任意の型が入ることを示しています。
ただし同じ名前の型変数の型は、全て同じであることに注意してください。
では変更したコードをGHCiにロードしてみましょう。
・・・エラーが発生してロードに失敗していますね。
エラーメッセージを読むと、ここまで意識していなかった「+」関数が関係しているようです。
エラーを解決するために、まず「+」関数の型を調べてみましょう。
ここでは(+)のように丸括弧で囲む必要があることに注意してください。
Num a =>
は初めて見る書き方ですが、これは型変数aに対して型クラス制約を行っています。
上記の一文を理解するためには、「型クラス」と「型クラス制約」という2つの概念について知る必要があります。
ただし以降の説明で出てくる「クラス、メソッド、インスタンス」という用語は、オブジェクト指向言語の文脈で使用する時と意味が異なる ことに注意してください。
ではまず1つ目の型クラスについて説明します。
型クラスとは「複数の型に共通する操作(メソッド)をまとめたもの」で、型クラスに属する 型(値ではない) をその型クラスのインスタンスと呼びます。
「+」関数の型宣言ではNum
が型クラスに該当します。
ここで型クラス自体は型ではないことに注意してください。
つまり型宣言の際に Num -> Num -> Num
のように記述することは出来ません。
上記を理解したところで型クラスNumがどのように定義されているか、GHCiのコマンドを使って調べてみましょう。
--
の右側はコメントであることに注意してください。
「+」、「-」、「*」などがメソッド、instance
キーワードを使って定義している、Word、Integer、Intなどがインスタンスです。
次に2つ目の型クラス制約について説明します。
日本語だと「型クラス制約のある型変数の型は、その型クラスのインスタンスでなければならない」と説明できます。
型変数をそのまま使うと任意の型を許容してしまうため、型クラス制約を型変数にかける必要があるということです。
上記のことを理解すると、先程のエラーメッセージの内容も理解することができます。
以下に箇条書きで記載すると、
と理解できます。
型に関するバグを実行前に防ぐことができるのは嬉しいですね。
ではサンプルコードを型クラス制約を追加したバージョンに変更してみましょう。
では変更したコードをGHCiにロードしてみましょう。
整数同士の加算も期待通りの結果が返ってきていますね。
書きはじめると型に関する簡単な説明だけで1つの記事になってしまいました。
Haskellにはカリー化、遅延評価、モナドなど興味深い概念が数多く存在しています。
どれも面白い概念なので、興味を持った方はぜひ調べてみてください。