信頼性はない
都市伝説かもしれん
大体枕詞に特別な理由がなければがつく
思いついたら徐々に更新する
それは違うよってことがあったら @Lugendre まで
Int であれば値は整数であるという条件だし, Num a => a であれば Num クラスのインスタンスである型 a という条件を表している. newtype で同じ内部表現を持つ別の型にしてしまうことである.(以下の例は拝借したもの)(拝借元を忘れる1敗)newtype Plain = Plain ByteString deriving (Show, Eq)newtype Encrypted = Encrypted ByteString deriving (Show, Eq)encrypt :: Plain -> Encryptedencrypt m = ... Plain , Encrypted 両方を受け取るような関数を作る場合,型クラスを持ち出す必要がある.内部表現は一致しているのに型を合わせるためだけに型クラスを持ち出すのはいささか面倒である.data Plain = Plaindata Encrypted = Encrypted newtype Message a = Message ByteString deriving (Show, Eq)encrypt :: Message Plain -> Message Encryptedencrypt m = ... Plain , Encrypted 両方で使えるような関数を定義する際, Message がパラメトリックなため,わざわざ型クラスを実装する必要ない.append :: Message a -> Message a -> Message aappend x y = coerce $ (coerce @_ @ByteString x) ++ (coerce @_ @ByteString y) newtype を使ったときよりも拡張性に優れている. newtype を用いて新しい型を定義する方法よりも一般に良いとされている. type2.hs の定義では Plain や Encrypted に値コンストラクタがあるが,これらの型は型タグとして使用されることだけを想定しているため,ライブラリ作成者の意図しない使用を制限するためにも値コンストラクタは存在しないほうが良い.幸い,GHCでは EmptyDataDecls 拡張によって値コンストラクタを省略したデータ型宣言をすることができる.これは現在のGHCではデフォルトで有効になっている. type2.hs の Message の定義を見てみよう.型変数 a に入る型は,右側のに現れない.つまり,型変数 a に何が入ろうが Message の内部表現が ByteString であることに変わりはない.このような振る舞いをする型変数を幽霊型 (Phantom type)と呼ぶ.これはこの論文で作られた造語である.論文ではEDSLに静的な型をつけることで,静的に書いた言語の正当性をある程度保証している. type2.hs の Message の実装には問題がある.それは期待されない型が型タグとして扱えてしまうことである.つまり, Message Int は,上記の実装では合法である.我々は, Message の型タグとして, Plain と Encrypted のみを期待しているので,これら以外を受け取らないように制限したい.素朴な考えとして, Message の型変数 a に型の型のようなもので注釈をつけられるとよさそうである.そのようなものは種(kind)と呼ばれている. * と,関数型の種バージョンの k1 -> k2 といった形のものと,制約を表す Constraint 種しか存在しないが,GHC拡張にはデータ型を種へ,値コンストラクタを型へ昇格する DataKinds という拡張がある.これを使えば,種と,その種を持つ型を定義することができる.data Status = Plain | Encrypted deriving (Show, Eq) Status 種を持つ型, Plain , Encrypted と Status 型を持つ値, Plain , Encrypted の2つが生成される.ここで注意すべきは,この2つの間になんの関連性も無いことである.型 Plain , Encrypted は対応する値コンストラクタは存在しない.つまり,値 Plain を使っても型 Plain を作ることはできない.これら2つが生成されるため,型コンストラクタと値コンストラクタを同じ名前にするというHaskellの悪しき風習に従ってデータ型を定義した場合,昇格された値コンストラクタと,元の型コンストラクタがだぶる.この場合,昇格された値コンストラクタの先頭に ' をつけることで区別することができる(e. g. 'Plain ).(昇格された値コンストラクタに ' ついていない場合,GHCはwarningを吐く) Message の型変数 a に種注釈をつければよい.デフォルトではこのような注釈はできないが,GHC拡張の KindSignatures を有効にすれば可能になる. data Status = Plain | Encrypted deriving (Show, Eq)newtype Message (a :: Status) = Message ByteString deriving (Show, Eq) Message Int のような型はコンパイル時に弾かれるようになる. Message を Vector に格納することを考えよう.残念ながら Vector には同じ型のものしかいれることができないため, Message Plain と Message Excrypted を一緒に Vector に入れることはできない.そこで,この2つの Message 型を同一の型として扱うことができないだろうか?こういった要求に答えるのが存在型である. data SomeMessage = forall a. Show (Message a) => SM (Message a) -- 存在型 hoge :: Vector SomeMessage -> Vector Boolhoge = ... SomeMessage が存在型である.ここで注目すべきは,幽霊型の時は逆に,右辺の型変数 a が左辺に現れないことである.この型変数を存在量化された型変数と呼び,この型変数により内部表現が変わろうとも型が変わらないということ表している.型変数を存在量化するには forall キーワードを使って, forall a b. といったようにする必要がある. SomeMessage の例のようにコンテキストも一緒に存在型の中に入れておけば,存在型の中の型がどのようなことができるかの情報が残り,コンテキストにある型クラスのメソッドと,それらをつかった多相な関数が扱えるようになる. unsafeCoerce でガッとやるなども使われたりする. MagicDict を参照せよ). id のような中の型に依存しないような関数であれば(意味があるかはおいておいて)適用できるのではなかろうか.次のような関数を書いてみよう.foo :: (a -> b) -> SomeMessage -> bfoo f (SM x) = f x a や b はこの関数の呼び出しのときに具体化されてしまうので, f :: a -> b は真に任意の型を受け入れられる関数ではないためである.真に多相な型変数を書く手段として, RankNTypes 拡張がある.これを使うと, type7.hs は以下のようになる.foo :: (forall a. a -> b) -> SomeMessage -> bfoo f (SM x) = f x a のスコープはかっこの中に限定される.つまり,型変数 a は関数の外に影響を受けないことが保証されるため,関数 f の引数は多相的であることが強いられる.これによって,任意の型を引数に取る多相な関数を表現することができる.