2017TweetHaskellには種(kind)という仕組みがあります。大雑把に言ってしまえば、「型の型」を実現する仕組みです。この仕組みについて、あまり情報が出回っていないようなので、解説記事を残しておこうと思います。BEGINNER)を対象に、種の仕組みとそれに付随するGHC言語拡張やパッケージを紹介するものです。断らない限り、対象としてGHC8系を設定しています。8以降に設定しておくことを推奨します。1このincrementという関数は、Int型の値を受け取って、1を加算したInt型の値を返します。error!種も大体同じようなものですが、種は型の形式が正しいかを検証する仕組みです。型の名前空間上に、Idという名前の型コンストラクタ値の名前空間上に、Idという名前の値コンストラクタを作ります1。aという型をしています2。aという型の値を作れる唯一のコンストラクタになります。aを構成できないことが、型システムによって保証できます。データ型宣言からは、型コンストラクタIdはそのままでは型になれず、何らかの型を受け取る必要があるように見えます。値コンストラクタは受け取った型aによって、その型が決まります。Maybeという一見おかしな型になってしまいます。このようにaにMaybeを渡すことは実際にはできません。みなさんお気付きだと思いますが、これを保証する仕組みが種なのです。この種がどういう意味を持つのかを見る前に、まずは種を分析するためのツールを用いて、型の種を見てみましょう。そのツールとは、GHCiのkindコマンドです。*ここで、typeコマンドは値の名前空間を、kindは型の名前空間を取っていることに注意してください。Intというようにtypeコマンドに型を分析させることはできません。*は型を二つとって型を返す型コンストラクタを表しているようです。型コンストラクタには部分適用もできるようです。...という形の種になるわけではありません。*種に()が付きました。この型コンストラクタAppIntは、単純に型をとるようなものではなく、型を一つとる型コンストラクタによって、型が作られます。Bool)ちょっと不思議な型ですね。*にマッチする型コンストラクタしか受け取れません。Either’エラー文がそのままですね。*を期待していたが、受け取った型Intの種は*ですよ」と言っています。*ですよ」と言っています。このようにして、型注釈によって受け取る値を制限できるように、種によって受け取る型を制限できるわけです。種がどのような意味を持っているのかを、ちゃんと見ていきましょう。tohere種の意味と種推論Haskellには、標準で二種類の種があります。k2という形をした種の二つです。k2は、k1の種を持つ型を受け取りk2の種を持つ型を返すような型コンストラクタをそれぞれ表します。Maybeなども一つのデータ型です。k2は右結合で解釈されます。右に括弧が付く場合は省略が可能ですが、左に付く場合は省略ができません。ここで一つ重要な型コンストラクタを紹介しておきましょう。それは関数型コンストラクタ(->)です。型コンストラクタが()で囲まれて、新しい表記方法が出てきたように思えますが、どうか落ち着いてください。通常の関数において(値の世界において)、私たちは中置演算子を()で囲むことで、通常の関数として扱うことができました。型注釈上でも同じようなことができます。a上の二つの型は、表記は違えど同じ型を表しています。実は私たちは、中置がデフォルトの型コンストラクタを自分で作ることもできます。それにはTypeOperators拡張を使わなければいけませんが。*残念ながらセクションは使えませんが、その他は大体中置演算子と同じで、部分適用などもできます。関数型コンストラクタ(->)も、(+)と似たようなものです。*というものに変更されたようです。この表記に関しては、続編の方で解説します。*であるとして話を進めていきます。(->)は二つの型を取り、データ型を返します。そのデータ型とは関数型です。Bool型の値を返す関数の型を表しているのでしたね。関数型コンストラクタは二つの引数の種を*に制限しています。Intといったような型注釈は書けません。これは、型コンストラクタが値を持たないことに反しません。Int)一番最初に見たデータ宣言です。このデータ宣言には、特に種に関する情報を書いているわけではありません。実は、種に関する推論によって、これらの種は決定されるのです。Haskellでは、型注釈なしの関数は、型推論されて型が決まります。種でも同じように、推論が行われます。*であったことを思い出してください。値コンストラクタは関数ですから、そのパラメータは*という種を持つことになります3。Idの方を考えてみると、値コンストラクタからa型は*という種であることが分かります。このようにして、IdとAppIntの種は自動的に決まったわけです。残念ながら、Haskellの標準システムでは、推論に頼らずデータ型コンストラクタの種を指定することはできません。*、*という型になります。TaggedData型コンストラクタのパラメータtも、どのような種であってもいいはずですが、*となります。このように、標準のHaskellでは、デフォルトで*が設定されており、確定しないような種は*として扱われます。型コンストラクタでタグ付けしたデータ型を作るといったことはできません。上では、種の意味と種推論について話しました。種推論は、種を推論してくれるわけですが、正しく私たちが思ったことを推論してくれるわけではなく、表現できない型コンストラクタもありました。その他にも推論が失敗するようなケースもあります。実は、このような場合につじつまが合う種はありません。aになりますが、この場合mが型コンストラクタなことは明白なので型コンストラクタに紐づく値が存在することになりますし、(->)の種にも合いません。一つ、推論が失敗する面白いケースがあります。*というような形をしているはずです。このように、両方に辻褄が合うような種を探していくと、永遠に同じ操作の繰り返しになり終わりません。このような場合も種の推論は失敗し、コンパイルエラーになります。型コンストラクタに型を渡す場合も、種がちゃんと合うかを確認し、種が合わない場合コンパイルエラーになるのでした。このように、コンパイルする際は、種の推論や種の検証を行い、辻褄が合うかを保障し、種を確定させる必要があります。Haskellではもう一つコンパイル時に行われる重要な評価があります。それは、型に関する評価です。Haskellのプログラム中の型を推論し、ちゃんと型の辻褄が合っているかも評価しなければなりません。これらの二つの評価はGHCでは別々に行われます。これは当たり前のように思えるかもしれませんが、種に関して考えるときは常に意識しなければなりません。*という種を持っているが、(->)が期待している種は*だ」と言っています。上のプログラムにはもう一つおかしな点があります。それは関数gの型注釈です。関数gの受け取る値はInt型のはずですが、実際にはChar型の値を受け取っています。関数gの型注釈の種に関しては何の問題もありません。GHCでは、種と型の検査は別々に行われるという話をしました。実は、さらにこの二つの間には評価順序があります。種の検査を行ってから、型の検査が行われるようになっているのです。種の検査に失敗すれば型の検査は行われません。これらは、:typeコマンドや:kindコマンドにも影響するので注意が必要です。:kindコマンドは種の評価を行いますが、型の評価は行いません。:kindコマンド上で種の検査が通って型の検査が通らないといった場面には遭遇しないかもしれないですが、これは心に留めておくと良いでしょう。tohereこの章のまとめこの章では、基本的な種の仕組みを紹介しました。種というのは、標準では二つ存在するのでした。k1の種を持つ型を受け取り、k2の種を持つ型を返す、型コンストラクタを表す種また、データ宣言において種は推論され、確定しない場合はデフォルトで*を用いるのでした。種と型の評価はそれぞれ別々に行われ、種の評価の後に型の評価が行われることも学びました。以降では、Haskell標準の種の仕組みを拡張する、幾つかの重要なGHC拡張について話していきましょう。tohere種注釈Haskell標準では、データ宣言において、型コンストラクタの種は種推論によって決定するのでした。このため、表現できない型コンストラクタがあることも話しました。これは、不便な場合があります。*の種を持つ型コンストラクタをタグとした、データ型を表現することができないのはもちろんですが、そもそも複雑なデータ型の場合に注釈としての種が欲しかったり、推論に任せずそもそも型の種を明示的に宣言したい場合があるのです。これは、Haskellにおいてトップレベルの関数の型注釈を行うことが、良い風習とされているのと同じですね。出来る人もいるかもしれませんが、混乱してしまう人もいるでしょう。c)))これならば、値コンストラクタの型について深く考えなくても、それぞれのパラメータがどういう種を持つ型なのかはすぐに分かるようになりますし、どういう意図で書いたのかが明白です。何の注釈もない場合、bにはMaybeを渡せばいいのか、それとも具体的な型を渡せばいいのか少し考える必要がありますが、注釈がある場合には種の読み方が分かっていればすぐ分かります。残念ながら、Haskellの標準でこのような注釈を書くことはできません。そこで、KindSignatures拡張の出番になります。KindSignaturesはその名の通り、種の注釈を可能にするGHC拡張です。この拡張により、データ宣言や型シノニムなどでも種注釈が書けるようになります。aこのプログラムでは、Appの方の種は見た目からすぐ分かります。上記の例では、すぐそばにAppのデータ宣言があるから分かりますが、AppとFlipAppの宣言が別々の場所にあることを想像してみてください。Haskellでは、型コンストラクタ(型関数)にはfやm、tをメタ変数として使う文化があるので、それから推測することは可能ですが、明確に知りたい場合には実装を見にいく必要が出てくる場合もあるでしょう。種注釈が書いてあれば、混乱を避けることができます。これが一つの種注釈の魅力です。種注釈を明示することで、推論に頼らず種の制約を書きたい場合もあります。あるケースはGADTs拡張を併用する場合です。GADTs拡張については、今回は詳しく扱いませんので、GADTsを知らない人は以下は読み飛ばしてください。aGADTsのスタイルは、値コンストラクタの型を明示的に書くため、型コンストラクタのパラメータ名を明記する必要がありません。型コンストラクタはその種が分かればいいですし、値コンストラクタはその型が分かれば問題ないからです。通常のデータ宣言では、型コンストラクタと値コンストラクタの型がごっちゃになっているため、このように種の注釈と型の注釈を完全に分離することは困難です。GADTsにおいて、パラメータ名に種注釈をつけていく書き方も許容されています。aただし、GADTsでは型コンストラクタのパラメータ名は特に意味を持たないことに注意してください。aを使わなくてもいい!このため、一番最初に提示したような、型コンストラクタにはその種注釈を、値コンストラクタにはその型注釈をそれぞれ書くというスタイルを好む人も多くいます。これも、一つのKindSignatures拡張の魅力と言えるでしょう。なにより重要なことは、GADTsでは値コンストラクタの型を明示しないといけないため、意図しない型コンストラクタへの適用を、誤って型注釈に書いてしまう可能性が、通常のデータ宣言より高くなります。種注釈をつけることで、型コンストラクタの意図している種を明示することにより、意図していなかった型コンストラクタの使用法が、種推論によってすりぬけてしまうことを防ぐことができます。tohere種多相さて、種注釈を行えるようにするKindSignatures拡張の他に、もう一つ重要な拡張があります。それが、種多相を行えるようにする拡張です。*という種になるのでした。*になればいいのですから、わざわざ*に強めてしまう必要はありません。kという形をしていれば良い」という情報を残すようにするのが、PolyKinds拡張、種多相の基本的な考え方です。私たちHaskellerは、多相関数で、具体化された型ではなく任意の型についてマッチするような関数を書くことに慣れています。多相関数の場合、具体化されていない型を型変数と呼ぶのでした。種の場合は種変数といったところでしょう。種多相は、GHCの標準パッケージbaseにおいて、様々なところで用いられています。有名なものとしては、Data.ProxyにあるProxyデータ型がそうです。tという値になります。このように実体(値)を持たない型パラメータを幽霊型と言ったりします。tというデータ型に成ります。Maybe不思議なデータ型ですね。種多相がなくても、Proxyデータ型のような幽霊型をパラメータに持つ型コンストラクタを作ることはできます。種によってそれぞれ型コンストラクタを用意しなければなりません。今回の例のように、種多相を使えば、一つのデータ宣言によって様々な種の型に対応できるようになるのが、魅力的です。PolyKinds拡張は、一緒にKindSignatures拡張も有効にします。これらを組み合わせることで、明示的に多相化された種の注釈を書くことも可能です。tag2このように種変数を使った種注釈も可能です。これを活用すれば、より強力な型コンストラクタを作ることも可能になるでしょう。tohereこの章のまとめこの章では、種に付随する、二つの重要なGHC拡張を紹介しました。KindSignatures拡張は、種注釈を行えるようにする拡張でした。種注釈によってこれまで表現できなかった型コンストラクタが作れるようになるのはもちろんのこと、分かりやすさや種推論による混乱を避けるための明示的な注釈として、この拡張はとても便利でした。一つのPolyKinds拡張は、種多相を可能にしてくれる拡張でした。標準では、全ての種は具体化され、曖昧なところは全て標準の種*によって具体化されます。この拡張によりデフォルトの動作を、抽象化されたまま型変数として残す動作に切り替えることができるようになります。これによって、それぞれの種に対しての具体的な型コンストラクタを用意する必要も無くなります。種注釈を多相的に行うことも可能になるのでした。tohereまとめ今回は、種の基本概念と、種に関連するGHC拡張を紹介しました。続編4では、*の他の幾つかの種と、種とは別の型の分類についての紹介などを踏まえた、幾つかの種に関連する話題について、話したいと思います。続きが気になる方は、読んでみてください。Haskell2010の仕様書です。標準の仕組みを紹介する際に参照しました。種の定義が書いてあります。種推論について書かれています。主な種に関する参考資料としてとGHC拡張についての資料として参考にしました。GHCにおいての種推論などの、種に関することが総括してあります。KindSignatures拡張の概要が書かれています。簡単にですが幾つか種に関する話題がまとまっています。参考にしていませんが、リンクとして置いておきます。この記事のストーリーを決める際に参照しました。続編では、このページを元にしたもう少し踏み込んだトピックも扱う予定です。続きが気になる人は、期待しないで待っててください。ライセンスの下に提供されています。Analyticsでアクセス情報を収集しています。集めた情報は統計的に処理した上で、当ウェブサイトの改善のための参考情報としてのみ使用します。