2017TweetHaskellには種(kind)という仕組みがあります。大雑把に言ってしまえば、「型の型」を実現する仕組みです。この仕組みについて、あまり情報が出回っていないようなので、解説記事を残しておこうと思います。前編と後編に分かれていて、この記事は後編になります。前編はこちらになります。1の続きとして、種に付随するGHC言語拡張やパッケージを紹介するものです。断らない限り、対象としてGHC8系を設定しています。7以降に設定しておくことを推奨します。前回の記事では、種の基本的な仕組みを紹介しました。*)など)しかありませんでした。GHCでは、他にもいくつか種を導入しています。今日は、その幾つかを紹介していきます。一つ目が、型制約を表す種Constraintです。この種を伴う仕組みはConstraintKinds拡張により導入できます。Haskellの型上には、データ型や型コンストラクタの他にも、型制約という登場人物がいます。型制約は名前の通り、型の制約が書けるようにするものです。yこの関数minByOrdは、型aが順序を持つ(Ordクラスのインスタンスである)という制約を満たしている時、二つの引数のうち小さい方をOrdのメソッド<を使用して返します。型制約は、型クラスを使うことで作ることができます。aこの型クラスを使うことで、デフォルトの値を持つ型制約を満たしている型上では、defaultValueメソッドを使用することができるようになります。aの値をパターンマッチし、中身がJustならそのままJustを外して値を返し、Nothingならデフォルト値をdefaultValueメソッドを使用して、返します。実はGHC上では、型制約にも種が割り当てられています。ConstraintHasDefault型クラスは、*の種を持つ型を受け取り、Constraintの種を持つ型制約を返します。ConstraintはGHCが導入している、型制約を表す種です。Constraintは、*の種の型、つまりデータ型を一つ受け取り、型制約になるような型上の関数になります。Constraint適用結果は、ちゃんとConstraintの種を持っています。kindコマンドは、型の計算は行わないのでした。私たちは、BoolをHasDefaultのインスタンスにしていないため、実際にはこの型制約は満たされません。型の計算を実際に行ってみましょう。True「HasDefaultはBoolに対してインスタンスを持っていない」と型エラーになっていることが分かります。このように型の計算はtypeコマンドで確かめることができるのでした。これらの型計算は、もう少し直接的に確かめることもできます。‘it’このように、型の計算だけを行わせる場合、undefinedを使用するのが便利です。Haskell標準では、型制約はあまり柔軟には書けません。具体的な型を伴う上のような制約も書けないため、FlexibleContexts拡張を使用することで書けるようにしています。上の型表記で登場する、=>という表記は、左で指定された型制約を満たしているならば右で指定された型付けの関数になる、という意味を持っています。*という種になります。Bool’kindコマンドによって、種が合わないとエラーになっていることが分かります。残念ながら、=>は実際には型演算子ではなく、(=>)というように型関数として扱うことはできません。それは表記上の問題であり、確かに種の計算の際、型制約を受け取る型演算子として、Constraintの種を持つか検査が行われていることは分かるでしょう。今までは型制約種Constraintについて、GHCi上で色々試しながら見てきました。型制約種に関しての雰囲気は分かってもらえたと思います。型制約種Constraintをもう少し詳しく見ていきましょう。Constraintデータ宣言と型クラス宣言を並べてみました。この二つはよく似ています。SimpleDataという値コンストラクタを作ります。aというような関数を作ります。作られるものがそれぞれ違いますが、両方型の世界に一つの型関数、値の世界に関数を作るわけです。型クラスの方をデータ宣言に合わせるとしたら、型クラスは型制約コンストラクタとメソッドを作るものと言えるかもしれません。型の世界だけでの話なら、データ宣言は型コンストラクタを、型クラスは型制約コンストラクタを単に作るだけの構文ということになります。ここで、データ型と型制約の対比を表にしてみましょう。...)両者はそれぞれ特別な意味を与えられています。実際、データ型が値を持ち1、それによって型注釈が書けるように、型制約は=>という特別に型制約を計算するような型上の構文を持っています。逆に言えば特別なのはそれだけで、それ以外に両者の違いはありません。*では、このProxyで型制約を受け取り、型制約の計算だけを行うような関数を作って使って見ましょう。String))()やっと、ConstraintKinds拡張の登場です。ConstraintKinds拡張は、型制約に関するHaskell標準の制限を幾つか取り払う拡張です。どのようなことが可能になるかは後で紹介するとして、今は上の関数の使い方に注目しましょう。この例のように、型制約はProxy型で持ち回し=>で任意のタイミングで型制約計算を行うといったことも可能です。上のProxyを使った例から明らかですが、もちろんデータ宣言時に種注釈を使うことで型制約を受け取るようなデータ型を作ることもできるわけです。型制約を受け取るような型クラスも作ることができます。Maybe)FlexibleInstances拡張は、FlexibleContexts拡張と同じような拡張で、FlexibleContextsは型制約の書き方の制限を、FlexibleInstances拡張はインスタンスの書き方の制限をそれぞれ取り払う拡張です。Constraint種はGHC.Extsモジュールに入っていて、使用する際はこのモジュールをimportする必要があります。これらを使って、上のようにすれば、型制約の分類分けすらすることができるようになります。他にもConstraintに関連する特殊な型上の演算子があります。普段気にも留めていなかったと思いますが、型制約のペアです。a最後の例ではConstraintKinds拡張を使用していますが、これについては最後にどんな拡張なのか説明しましょう。今、注目してもらいたいのは、型制約のペアについてです。普段何気なく使っていると思いますが、これらも一種の型制約の演算子と見ることができるわけです。注意してもらいたいのが、この演算子はタプル型と同じ形式だということです。Bool)’()は、ユニット型の方が優先されています。(,)は、最初に書いた型の種によって、受け取る種が左右されていることが分かりますね。Bool)というような表記は許容されていませんが、それ以外はあまりタプル型と変わりありません。異なるのは、タプル型が幾つかのデータ型を受け取って一つのデータ型となるのに対し、型制約のペアは型制約を幾つか受け取りそれを全て満たすような型制約になるということです。最後にConstraintKinds拡張をきちんと紹介しておきましょう。ConstraintKinds拡張は、次のようなことを可能にしてくれる拡張です。型エイリアスと同じ構文で、型制約コンストラクタのエイリアスを書くことができるようになる。Functor型制約種Constraintを持つ型を、型制約として使用できるようにする。こちらは、あまり実感が湧かないかもしれません。デフォルトで、GHCでは型クラスなどを型制約として扱う、つまり=>に渡すことができます。a)という形式のものは型制約種を持ってはいますが、標準で許容されている形式ではないからです。このような場合に、より柔軟に扱えるようにしてくれる拡張が、ConstraintKinds拡張です。型制約種Constraintについて、馴染んでもらえたでしょうか?普段、この種やConstraintKindsを明示的に使うような場面は少ないかもしれませんね。型制約種について興味を持ったなら、constraintsというパッケージを見てみるのが良いでしょう。このパッケージは、型制約プログラミングに関する幾つかの有用なAPIを提供しています。k2といった、予め用意された特別な種を紹介してきました。GHC上で、私たちが種を定義するような方法も、実は用意されています。それが、DataKindsという拡張です。DataKindsは基本的には簡単な拡張です。aというデータ型の値を作る値コンストラクタをそれぞれ作るのでした。DataKindsは、このそれぞれのコンストラクタを、一つ上の層に昇格させることができるようになる拡張です。a最後の実行例に注目してください。ここで書かれているSimpleDataは値コンストラクタのものです。先頭に'(シングルクォーテーション)がついていますが、何より注目すべきなのは、種の表示にもやはりSimpleDataというものが現れていることです。これが一つ上の層に昇格させるということになります。aを昇格したものになります。値コンストラクタSimpleDataは型多相化されたコンストラクタなので、それを昇格させた'SimpleDataは種多相化された型コンストラクタになります。aのaは、任意の種を、それぞれ表すということに注意してください。種多相化されていることを確認してみましょう。Constraint)Proxy型のように、どんな値でもとれるようになっていることが分かると思います。Monoidという型を持つような値は存在しないということです。値を持つ型は全て、*という種を持つのでしたね。aという種は*と一致しないため、値を持たないということもできます。一つの活用例としては、データ型のタグに利用ができます。...この例は、HTTPのリクエストが、どんなメソッドでのリクエストかを、タグ情報で持つような例です。このタグ情報によって、処理を型安全に分けることができます。以下の問題点があります。各メソッドのタグが別々のデータとして宣言されていて、集約されていません。Boolといった型を作ることもできるようになってしまいます。DataKindsを使うことで、もう少しタグ情報を明確に書くことができます。...この例では、DataKinds拡張を使うことで、前の例での欠点を修正しています。メソッド情報はHttpMethodというデータ型の宣言に集約していますし、種に昇格させたデータ型で種注釈を行うことで、RequestはHttpMethod以外の型がとれないようになっています。このように、DataKindsは値を持ちませんが、タグを表す型としてとても便利です。その他にも、DataKinds拡張は、シングルトンというものを定義することによって、より有用になる場合があります。これらの話は種の話題というよりは型レベルプログラミングの話題になるので、この記事では紹介しません。興味がある方は、singletonsという有用なパッケージがあるので、見てみると良いでしょう。b)などにも適用できます。リスト型の昇格から見ていきましょう。[Bool]といったようなものが書けるのでした。これらをそれぞれ昇格させたものが上のものになります。d)タプル型もリスト型と大体同じような感じですね。ここからは、DataKindsのもう少し詳細な見方を紹介しておきましょう。DataKindsは型コンストラクタを種上に昇格、値コンストラクタを型上に昇格させることをできるようにするような拡張でした。k2という種も昇格された種とみなすことができます。‘GHC.Types’見ての通り、GHC.Typesというモジュールで定義された、値コンストラクタを持たないデータ型です。*が、種に昇格可能なこと型制約は、Constraint型が昇格された種に結び付けられることだけで、他はDataKindsと同じです。昇格前は単純に値も型引数も持たないデータ型です。*確かにConstraintが、*を種に持つ型であることが分かりますね。k2の方はちょっと特殊で、関数型コンストラクタ(->)が昇格したものになっています。関数型は関数に結びついているデータ型でした。`(->)`...関数型もやはり値コンストラクタを持ちません。Constraintと違い、関数型は関数という値を持ちます。bという型を持つ値は、a型の値を受け取りb型の値を返すような関数になるのでしたね。これらの関数を作る操作、例えばラムダ記法や関数宣言などが、関数型の値コンストラクタと言えるでしょう。bという種は、aの種を持つ型を受け取り、bの種を持つ型を返すような、型上の関数を表します。bという種の型関数に昇格という感じの対応をすることになります。見ると、少々特殊ではありますが、DataKindsでの昇格したデータ型と同じような扱いと思うことができます。k2でさえ、DataKindsの昇格と同じように見ることができます。*はどうでしょうか?実は、*だけは少し特別です。*さて、GHC.Typesモジュールには、データ型*が定義されています。このデータ型は自身を、つまり*の昇格された種を持っていると見ることができます。*とは、上の型が種に昇格されたものとなるわけです。*これらをもっと視覚的にまとめてみましょう。aであることに注意してください。Boolとすることも可能です2。このように見てみると、私たちが種と呼んでいたものは、単にある型に付属する単なる型情報だと思えてきます。種注釈とは、単にその型がどういう型に付属しているかの情報に過ぎないのです。値にもやはり型情報が付属しています。値とDataKindsによって型に昇格したものを同一視してみると、値と型の間には差異はないということになりますね。いう拡張が提供されています。この拡張は後ほど紹介しましょう。最後に名前空間の話をしておきましょう。全ての種は、ある型が昇格したものである、という話をしました。GHCでは、そういう背景があり、種の名前空間は型の名前空間と完全に一致します。値の名前空間と型の名前空間を完全に一致させることはできません。それはHaskellが多用している、型コンストラクタと値コンストラクタの名前を同じにするという文化があるからです。aBの例は、'を書いていないのに型に昇格できていますね。DataKindsは値と型の名前で被るものが無いようなものは'を書かなくていいようになっています。これは型から種へ昇格できる時は自然な動作でしたが、値から型の場合、今回でいうAのように型コンストラクタと値コンストラクタの名前が被ってしまうケースが出てきます。データ型Bが新しく宣言されてしまうような場合もあるでしょう。このように、型から種へは名前空間が一致しますが、値から型へは名前が被るのを避けるために'を付けるようにしているのです。本質的には値から型へも、'を付けないで昇格させることが理想です。それを覚えておきながら、DataKinds拡張使用の際は、'を適切に付けていくのが良いでしょう。最後に、少し変わった型と、それにまつわる種の分類分けについてお話ししましょう。この見方は、よりGHCのプリミティブな部分に携わる時に、役にたつはずです。k2しかありませんでした。GHCでは、それに型制約種Constraintが追加されてるのでしたね。DataKinds拡張を使えば、データ型を種に昇格することもできました。結局値を持つ型は*という種を持つのでしたね。この制約には一部例外があります。それは、GHCのプリミティブな値についてです。私たちは普段何気なくInt型やDouble型を使用しています。ここでは詳細な話はしませんが、幾つか基本的なGHCでの内部表現についてお話ししましょう。GHCiで、この二つの型の情報を表示してみましょう。‘GHC.Types’どうやらこの二つの型は、不思議な値コンストラクタを持っているようですね。Doubleを持ちます。Int#やDouble#といった見慣れないデータ型が出てきましたね。これらが今回紹介するGHCの用意しているプリミティブなデータ型です。上の値コンストラクタを使って、それぞれのデータ型の値を作ってみましょう。それにはMagicHash拡張が必要です。通常、#のついた値や型は、私たちは扱うことができません。それを可能にするのがMagicHash拡張です。IntやDoubleの値は、1といった数値リテラルで作れるのでした。同じように、Int#やDouble#といった不思議なデータ型にも、それぞれのリテラルが用意されています。Double各リテラル表記は、次のようになっています:Int#のリテラルは、整数と#で作ることができます。Double#のリテラルは、実数と##で作ることができます。他にも幾つか#の付くデータ型があるのですが、まずはこのデータ型がどのようなものなのかについて、紹介しましょう。