GHCでは、多くのデータ型はボックス化、つまりヒープ上に参照データとして格納されています。データ型の値自体はポインタで、本体はヒープ上にあるというわけです。これは、データ型が、値コンストラクタを複数持つ場合もありますし、複数の様々なデータ型を取るパラメータを持つこともあり、サイズが多岐に渡るからです。ヒープに格納するには、格納する場所を計算して領域を確保し、必要なくなったら領域を解放しなければならないという、大きなコストがかかります。これは、サイズが大きなデータ型については、いちいち領域をコピーし実データのまま扱うよりも、低コストになる場合が多いですが、サイズが固定されていて尚且つ小さなデータの場合、大きな足かせになります。このため、GHCは幾つかの特別なデータ型を用意し、そのデータ型はヒープ上に格納せず直接実データとして扱うようにしています。それが、非ボックス型です。GHCは非ボックス型の他にも幾つか特別なデータ型を用意しています。Array#やMutableArray#、MVar#などのデータ型です。これらは、実データとしてではなくヒープ上に格納され、ポインタをデータとするようなものです。ボックス化されているわけです。通常のデータ型と異なり、ヒープ上のデータは特殊な構造をしています。このように、GHCが用意している、Haskell上では定義できない特殊な構造を持つデータ型をボックス化されている/されていないに関わらず、プリミティブ型と言います。まとめておきましょう:プリミティブ型とは、GHCが事前に用意している、Haskellの構文では定義できない、値が特殊な構造を持つようなデータ型のこと。非ボックス型とは、ボックス化されていない型、つまりポインタとして表現されるのではなく実データとして表現されるような値を持つ型のこと。この記事は種についての記事なので、種の話もしましょう。プリミティブ型は、GHCでは*ではなくそれぞれが特別な種を割り当てられています。'GHC.Types.DoubleRepなにやら、不思議な種が登場しました。8.2.1のものですが、これらのデータ型は現在かなりアグレッシブな変更が加えられており、表現方法がバージョンによってかなり異なります。*、共に特に難しい定義ではありませんね。representation”(実行環境での表現)という意味を表した名前になっています。前の章でのデータ型の昇格の話を思い出してください。'IntRepという型が昇格したものになります。'IntRepは、RuntimeRepデータ型の値コンストラクタIntRepが、型に昇格したものということを思い出してください。値IntRepを型に昇格した'IntRepが、さらに種に昇格しているということになります。型が合ってるかは、昇格される前の型で調べれば良いのですね。*ちゃんと種が符合していることが分かりますね。bなどと同じく、DataKinds無しで特別に種に昇格することが許可されています。この特別なデータ型によって、プリミティブ型は表現されています。-この他にもGHCはプリミティブ型を用意しています。プリミティブ型は、ghc-primパッケージのGHC.Primモジュールにて公開されています。興味があれば、種を確認しながら見てみると面白いでしょう3。TupleRepやSumRepを持つ型は、それぞれUnboxedTuples拡張、UnboxedSums拡張を使用する必要があります。こちらについても、気になる方は調べてみてください。プリミティブ型は、その表現方法によって種が用意されていることは分かったと思います。最後に、このRuntimeRepの中で二つの特殊な要素LiftedRepとUnliftedRepについて話しておきましょう。データ型を表す種として、*を紹介しました。'LiftedRepつまり、今まで見てきたデータ型は、実行時にliftedという枠組みで表現されるようなものというわけです。それの説明に入る前に、GHCにおいての評価戦略とデータの内部表現についての話をしておきましょう。ときに、Haskellは遅延評価デフォルトの言語です。exception"という式は評価されるまでは実際の値でなく、サンクという計算式を表現したデータとして保持されます。サンクは一度評価されると破棄され、実際の値にすげ変わります。上の関数fが受け取るのは、このサンクまたはすげ変わった実際の値を指し示すようなポインタです。exception"という、まだ評価されていない式のサンクを指し示すポインタというわけです。上の例では、関数fに渡されたサンクを示すポインタは、特に評価されないまま捨てられ、定数値が返ってきます。interactive:Ghci2この例では先ほどと違い、受け取ったサンクを関数fの中のif文で評価しています。そのため、例外が発生しているわけです。サンクはGHCが遅延評価を実装するための仕組みであり、lifted型の値は指し示す先がサンクになり得るようなポインタで表現されます。lifted型とは、その値としてボトムと称される値になるようなものも持てるような型のことです。無限ループになっていて永遠に評価が終わらないような値Haskellの通常の型は、上のような式を表すサンクをも値として持ちますから、無限ループになったり例外が出されたりするような値、つまりボトムをも表現できます。それに対して、GHCではボトムを表現できない型も存在します。その典型が非ボックス型です。非ボックス型はポインタではなく、実データとして表されているんでしたよね。実データは、サンクとすげ替えるということができないですよね。今回、関数fはやはり受け取った値を無視して定数を返します。どこにも受け取った引数を評価する箇所はありません。liftedの例(Intの例)では、undefinedを評価せず、つまり例外が一切出ずに定数が返ってきました。今回の非ボックス型の例(Int#の例)では、例外が発生しています。実は、非ボックス型は遅延評価ではなく正格評価が行われます。非ボックス型として値を格納するならば、サンクではなく評価した後の実データでないといけません。そのため、ボックス型の引数を受け取る関数の場合は、一旦引数に渡される式を評価して実データにした上で、関数に渡すということを行います。実は、これは非ボックス型だけに止まりません。GHCでは、ボックス型の中にも正格評価になるような、つまりサンクを値として持たないような型があります。'UnliftedRepを種に持つデータ型です。aはヒープ上に本体があり、それを指し示すポインタで表現されます。このポインタは、サンクを指し示すことはありません。実データを指し示すことになり、ボトムを値に持つことはないのです。Int#は、GHCで用意されているInt#専用の加算演算子です。この関数は問題なく定義することができますが、実行すると無限ループを起こします。Int#というような式はボトムを表しているように見えます。1#という式は、それ単体ではHaskellでは単なる表記に過ぎないということです。この式は、なんらかのトップレベル関数や定数の一部になっているはずです。*であることを思い出してください!)。関数はコンパイルされ、ランタイムによって実行されます。最終的に実行時に意味を持つのは、トップレベルの関数であり、それはliftedな型で表現されるということです。Haskellではunlifted型のトップレベル定数の宣言は許されていません。1#これにより、トップレベルの関数や定数は、全てliftedな型を持つことになります。内部でunliftedな式が無限ループや例外を吐くなら、それはその式を含んだトップレベルのliftedな関数や定数が、ボトムを表すサンクを持つことになるということです。これは、unliftedの考え方を逸脱しません。トップレベルの関数や定数はサンクを持つliftedな値に翻訳され、unliftedな型を持つ引数は、受け取る前に正格に評価され、サンクを持たない値となった後関数に渡されます。このような解釈によって、Haskellでのlifted/unliftedの枠組みは保たれます。ボトムについての形式的な議論は、領域理論という分野でされています。lifted/unliftedについての理論的な背景が知りたいなら、領域理論や表示的意味論について学習してみると良いでしょう5。ここまでのことを大雑把にまとめておきました。サンクを持ちボトムを値として含むような型か、サンクを持たない型かなお、非ボックス型はunliftedであり、liftedな型はボックス型になります6。tohereこの章のまとめこの章では、型制約を表す種Constraintの紹介、型を種に、値を型に昇格するDataKinds拡張の紹介、そしてプリミティブ型の種とGHCの型の大別について、お話ししました。型制約には型制約種Constraintという種がつくのでした。データ宣言が型コンストラクタと値コンストラクタを作るように、型クラスは型制約コンストラクタと型制約下でのメソッド群を作るものとみることができました。型制約は、=>によって制約が満たされるか検査されるのでしたね。Haskell標準では型制約は決まった形状でしか書けませんでした。そのため、ConstraintKinds拡張が用意されており、この拡張によって型制約種を持つものならば変数であろうと型制約のペアであろうと、型制約として扱えるようになるのでした。この拡張によって、型制約のエイリアスも書けるようになりました。DataKindsはデータ型の型コンストラクタを種において使えるように、値コンストラクタを型において使えるようにするものでした。値コンストラクタは、昇格の際先頭に'をつけるのでした。Haskellの種全般が、何かしらの型が昇格したものとみなせるという話もしましたね。*ですら、一つのデータ型でした。最後に、GHCのプリミティブ型、ボックス型、lifted型という大別を紹介しました。プリミティブ型は、Haskellでは定義できないGHCが事前に用意してくれている型でした。Int#、Array#などがそうです。ボックス型の値は、ポインタで表現されヒープ上に本体を持ちました。非ボックス型の値は、実データとして表現されます。lifted型の値は、評価されるまではサンクを指すポインタとなっており、undefinedなどの評価すると例外になるようなものや無限ループでさえ値として持ち得るのでした。unlifted型の値は、サンクを持たず、正格に評価されるのでした。以降では、少し高度な種に関する話題を紹介していきます。知られてない機能や最近入った機能、まだ入ってない提案中のものなども紹介していきます。これらの話題は、最初に掲げた想定読者層から外れているのであまり詳しくは紹介しません。こんな話もあるんだぐらいに留めておいてもらえれば、良いでしょう。今までは、種に関する基本的な話題を紹介しました。ここでは、種とは別の、もう一つの型に付属する種別情報を紹介しましょう。roleと呼ばれるものです。roleは、GeneralizedNewtypeDerivingという拡張と、密接な関係があります。ここでは詳しく解説しませんが、GeneralizedNewtypeDerivingという拡張は、newtypeで作った型のクラスインスタンス導出を簡略化するための拡張で、そのインスタンスを元の型のものを持ってきて実装します。この拡張は利便性を向上させますが、その導出が壊れるケースが出てきます。Boolと同じであるということに注意してください。Ageにキャストすることは型の健全性を壊します。roleは、二つの型が同じ表現を持つ型かどうかを判断するために、組み込まれた機能です。キャストが型安全にできるかを判断するためのものなのです。bが同じ表現を持つかを判断するための機構で、Tのどのパラメータが判断するときに関与するか、という情報を持ちます。受け取ったパラメータの型が同じ表現であるならば、全体としても同じ表現になることを示します。roleは推論によって決定するようになっているため、私たちは普段特に意識する必要はありません。推論結果が意図しないものである場合もあります。b_は推論に任せることを意味します。Rolesを読んでみると良いでしょう。GHCに簡潔にまとまっています。前の章では、プリミティブ型の種を紹介しました。*という型コンストラクタを昇格したものによって、作られているのでした。'LiftedRepのエイリアスでしか無かったのでしたね。このような種の表現になったのは、実は最近のことです。昔はそれぞれの種は実行時の表現ごとに切り離されていました。いう話題と密接な関係があります。軽率多相は、種多相を少し制限したものです。種多相は任意の種を扱えるような種変数を許容しますが、軽率多相は実行時表現に関係するような範囲での種の多相を提供します。*であると話してきました。*というような種を持っています。TYPEの引数の部分は、任意のRuntimeRepをとれるようになっています。errorTypeInType拡張については、後ほど解説しますので、今はおまじないとしておきましょう。上の例では、任意のRuntimeRepに対して、確かに関数型コンストラクタが有効であることが確認できます。このように関数型コンストラタは軽率多相化されています。Int#といったような関数を、通常の関数型の表記で書けるようになっていたのです。関数型が軽率多相化されているということは、軽率多相化された関数が書けるということでもあります。実際、幾つかの関数は軽率多相化された形で提供されています。($)やerrorなどがそうです。軽率多相は全てに気軽に適用できるというわけではありません。xこの例は、($)を軽率多相化した例になります。この実装には幾つかの問題があります。badをコンパイルすることを考えてみてください。badの引数fはただの関数なので問題ありませんが、引数xは軽率多相化されています。私たちはコンパイル時に、この質問に答えることはできません。実データである場合もありポインタである場合もありますし、サンクを持っているかもしれません。私たちは、badの引数xをどのように扱えばいいのか、コンパイル時に決めることができないのです。これは、引数が軽率多相化された関数全てに対して当てはまることです。このため、GHCでは軽率多相化したような変数を使った関数宣言は許可されていません。fこの場合、軽率多相化された変数はどこにも出てきていません。ところで、軽率多相の焦点は、実行時表現の中でもlifted/unliftedの枠組みについてです。liftedな型の値はサンクを持ち遅延評価を主とし、unliftedな型の値はサンクを持たないので正格評価になるのでしたね。このサンクを持つ持たないに関わらず関数を多相化して書けるようにするのが、軽率多相の主な目的です。GHCではもう一つ重要なboxed/unboxedという枠組みもあるのでしたね。version)を読んでみるのがいいでしょう。この論文では、軽率多相に至るまでのGHCでの経緯と問題点、軽率多相の動機などが丁寧に解説されています。polymorphismにも、GHCでの軽率多相についての仕組みが書かれています。tohereトップレベル種注釈KindSignatures拡張は、種の注釈を書けるようにするようなものでした。KindSignaturesは型パラメータの種を明示的に書くことができるようにする拡張でしたね。種が明示されないパラメータは、種推論によってその種が定まります。種推論は幾つか制約があり、その一つに再帰的データ型に対しては、単相的な再帰の種推論しか行えないというものがあります。*というような多相化された種でも問題ないはずですが、実際にはPolyKindsをつけているにも関わらず*で単相化されて推論されます。CUSKは、種多相な再帰的データ型を提供する場合必須のものになってきますが、上の例からも分かる通り非常に見にくいのが難点です。構文を解析してCUSKか判断するのにも、手間がかかります。いう機能が提案されています。a))関数の型注釈などと同じスタイルで、非常に見やすいですね。#54で提案されています。recursionと合わせて提案内容を見てみると良いでしょう。tohereUnliftedデータ型Haskellのデータ型は、liftedという枠組みの型でした。liftedな型は、評価されるまではサンクになっているのでした。unliftedな型は、GHCで幾つかプリミティブ型として提供されているのでした。現在、unliftedデータ型という拡張が提案されています。この拡張は、ユーザー定義のunliftedな型を定義できるようにする拡張です。UFalseここで、UBool型の値は、unliftedな型でありボックス型であるようなデータ型になります。aと同じようなデータ型で、サンクを持たずポインタでヒープ上の本体を指し示すような表現がされます。'UnliftedRepになります。この拡張下では、unlifted型のnewtypeが行えるようにするという提案もされています。UBoolこれにより、新たな構文を導入しなくても、unliftedなデータ型を定義できるようになります。UnliftedDataTypesのページを見てみると良いでしょう。このページに、主な提案内容が書かれています。TypeTypeInType拡張は、DataKinds拡張とPolyKinds拡張をより強力にした拡張です。*このように、型注釈でできたことが、種注釈でできるようになるわけです。この拡張は現状とても不安定であり、使用が推奨されているわけではありません。この拡張を使用する場合は、コンパイル時、内部でこの拡張の挙動をチェックするように-dcore-lintというフラグを使用することが推奨されています。将来的には、この拡張の範囲を型と種の範囲から、型と値の範囲、ひいては種と型と値の違いを取り払い、依存型というシステムに徐々に近づけていくことも視野に入れているようです。は種と型の範囲で安定的な機能を提供するのが、目的ということでしょう。Type-in-Typeに、その概要が書かれています。tohereこの章のまとめこの章では、幾つかの種に関する話題をかいつまんで紹介しました。roleは、二つの型が同じ内部表現を持つ型かを判定するための、型変数が持つ種とは異なる情報でした。roleを割り当てるかが推論されるのでした。RoleAnnotations拡張によって明示的に指定することも可能なのでした。polymorphism)はlifted/unliftedの違いを吸収する多相でした。これによって、lifted/unliftedの違いを問わない多相化された関数を書けるようになるのでした。全ての関数を制限なく軽率多相化することはできず、関数の引数が軽率多相化されているようなものは、機械語にうまく翻訳できないため書けないのでした。トップレベル種注釈は、既存のCUSKという種注釈の方法に変わり、種注釈を分かりやすく書くための提案でした。再帰的データ型に対して種推論がうまく働かないという制約から、多相的な種を使用する場合、種推論に頼らず完全な種の情報を提供する必要があり、完全に情報が提供されるような種の注釈をGHCではCUSKと読んでいるのでした。CUSKは一般的に見づらいため、それを解決するための提案でしたね。この提案されている構文は、関数の型注釈と同じスタイルで非常に分かりやすいですね。unliftedデータ型は、unliftedな型を定義できるようにするような拡張として提案されているものでした。この拡張により、ボックス型でかつunliftedなもの、つまりサンクを持たないボックス型を定義できるようになるのでした。現状、幾つか議論されるべき課題が残っていますが、近い将来導入されるかもしれません。TypeInTypeは型と種の違いを取り払うような拡張でした。これにより、型エイリアスを種に昇格したり、RankNの種注釈を書けるようになるのでした。現状は非常に不安定であり、使用には注意が必要です。1と合わせて、一通りの種に関する話題を紹介してきました。種の仕組みの紹介と5つの大きな話題を取り扱ってきました。高度な話題を幾つか、駆け足で紹介しました。この二つの記事が、何かの役に立てば幸いです。3を書くかもしれませんが、ひとまずはこれで。Haskell2010の仕様書です。標準の仕組みを紹介する際に参照しました。標準の型システムや型制約について、書かれています。主な種に関する参考資料としてとGHC拡張についての資料として参考にしました。非ボックス型を主とするプリミティブ型と、その演算のために用意されている関数についての解説が書かれています。DataKinds拡張の動機と解説が書かれています。GHCにおいての種推論などの、種に関することが総括してあります。軽率多相に関するGHCでの主な仕組みや制約について解説されています。型制約に関する、GHC上のいくつかの話題が書かれています。roleについての、動機と解説が書かれています。GHCでの実装面、言語面での提案を管理するリポジトリです。8.2.1での軽率多相に関する変更が書かれています。トップレベル型注釈に関しての提案です。GHCの実装に関する事や、その元となるアイデアについてまとめられているWikiです。この記事のストーリーを決める際に参照しました。GHCの型にまつわる内部表現や分類法などについて書かれています。GHCオブジェクトの内部表現について書かれています。lifted/unlifted、boxed/unboxedの違いについて、参考にしました。PolyKinds拡張に関する話題がまとまっているページです。unliftedデータ型を定義できるようにするための拡張の提案が、まとめられているページです。軽率多相以前のGHCの仕組みとして、OpenKindというものがありました。この仕組みは幾つか問題が知られており、現在は軽率多相によって置き換えられています。ここでは、OpenKindの仕組みと問題点、その解決法が書いてあります。DataKinds拡張の提唱論文です。DataKindsについて紹介する時、参考にしました。軽率多相の提唱論文です。軽率多相について紹介する時、参考にしました。非ボックス型の提唱論文です。unliftedな型の意味を紹介する場合に、参考にしました。Intなど型を一つ渡して初めて値を持つような型になるのでした。↩ここでのBoolは、Bool型が種に昇格したものという点にも注意してくださいね!DataKinds拡張によって、データ型は種に昇格できるのでした。↩VecRepを持つプリミティブ型は紹介しませんでしたが、この型はSIMDベクトル演算を利用するために用意されています。APIのどのデータ型を使用するかを表します。Vectorsの章にまとまっているので、興味があれば見てみると良いでしょう。↩この種の定義は、GHC.Typesモジュールで確認することができます。他にも、エイリアスとしてTypeやUnicode版の★が用意されています。Theoryを読んでみるのがよいでしょう。この文献は、領域理論に必要な順序理論の知識から、領域理論の基本的な概念を解説してくれている文献です。理論自体に興味がなく、この理論がどのような問題解決を目指しているかだけを知りたいなら、Originsだけでも読むと良いでしょう。逆にliftedならば必ずポインタで表されているはずなので、ボックス型になります。ボックス型だからといってliftedであるとは限りません。ライセンスの下に提供されています。Analyticsでアクセス情報を収集しています。集めた情報は統計的に処理した上で、当ウェブサイトの改善のための参考情報としてのみ使用します。