私は誰もがフォームの一般的なメソッドに精通していると確信しています:
T DoSomething<T>(T item)
この関数は、パラメトリックポリモーフィック(PP)、特にrank-1 PPとも呼ばれます。
このメソッドは、次の形式の関数オブジェクトを使用して表すことができるとします。
<T> : T -> T
あれは、 <T>
は、1つの型パラメーターを取ることを意味し、T -> T
は、タイプT
のパラメーターを1つ取り、同じタイプの値を返すことを意味します。
次に、次はランク2になりますPP関数:
(<T> : T -> T) -> int
関数自体は型パラメーターを取りませんが、型パラメーターを受け取る関数を受け取ります。これを繰り返し続けて、ネストをどんどん深くして、PPより高いランクに)することができます。
この機能は、プログラミング言語では非常にまれです。 Haskellでさえ、デフォルトでは許可されていません。
役に立ちましたか?他の方法では説明が難しい行動を説明できますか?
また、何かがimpredicativeとはどういう意味ですか? (この文脈では)
一般に、calleeではなくcalleeで型パラメーターの値を選択できるようにする場合は、より高いランクのポリモーフィズムを使用します。例えば:
f :: (forall a. Show a => a -> Int) -> (Int, Int)
f g = (g "one", g 2)
このg
に渡すすべての関数f
は、あるタイプの値からInt
を取得できる必要があります。ここで、onlyのことg
は、その型についてShow
のインスタンスがあることを知っています。だからこれらはコーシャです:
f (length . show)
f (const 42)
しかし、これらはそうではありません:
f length
f succ
特に有用なアプリケーションの1つは、typesのスコープを使用してvaluesのスコープを強制することです。タイプAction<T>
のオブジェクトがあり、futureやコールバックなどのタイプT
の結果を生成するために実行できるアクションを表しているとします。
T runAction<T>(Action<T>)
runAction :: forall a. Action a -> a
ここで、Resource<T>
オブジェクトを割り当てることができるAction
もあるとします。
Action<Resource<T>> newResource<T>(T)
newResource :: forall a. a -> Action (Resource a)
これらのリソースがonlyが作成されたAction
内で使用され、異なるアクション間または同じアクションの異なる実行間で共有されないようにして、アクションが確定的であり、繰り返し可能。
S
およびResource
タイプにパラメーターAction
を追加することにより、より高いランクのタイプを使用してこれを実現できます。これは完全に抽象的です。これは、 Action
。今私たちの署名は:
T run<T>(<S> Action<S, T>)
Action<S, Resource<S, T>> newResource<T>(T)
runAction :: forall a. (forall s. Action s a) -> a
newResource :: forall s a. a -> Action s (Resource s a)
これでrunAction
にAction<S, T>
を与えると、「スコープ」パラメーターS
は完全に多態性であるため、runAction
の本体をエスケープできないことが保証されますしたがって、Resource<S, int>
などのS
を使用する型の値は同様にエスケープできません。
(Haskellでは、これはST
モナドとして知られています。ここで、runAction
はrunST
と呼ばれ、Resource
はSTRef
と呼ばれ、newResource
はnewSTRef
と呼ばれます。)
上位のポリモーフィズムは非常に役立ちます。システムF(型付きのコア言語FPおなじみの言語))では、これは実際にシステムFがプログラミングする「型付き教会エンコーディング」を認めるために不可欠です。これらがなければ、システムFはまったく役に立たない。
システムFでは、数値を次のように定義します
Nat = forall c. (c -> c) -> c -> c
加算にはタイプがあります
plus : Nat -> Nat -> Nat
plus l r = Λ t. λ (s : t -> t). λ (z : t). l s (r s z)
これはより高いランクタイプです(forall c.
はこれらの矢印の中に表示されます)。
これは他の場所でも起こります。たとえば、計算が適切な継続渡しスタイルであることを示したい場合(google "codensity haskell")、これは次のように正しいでしょう。
type CPSed A = forall c. (A -> c) -> c
システムFの無人タイプについて話す場合でも、より高いランクのポリモーフィズムが必要です。
type Void = forall a. a
長くて短く、純粋な型システム(システムF、CoC)で関数を作成する場合、興味深いデータを処理するには、より高いランクのポリモーフィズムが必要です。
特にシステムFでは、これらのエンコーディングは「包括的」である必要があります。つまり、forall a.
は絶対にすべてのタイプを定量化します。これには、私たちが定義しているタイプそのものが決定的に含まれます。 forall a. a
では、a
が実際にforall a. a
を表す可能性があります。 MLのような言語ではこれは当てはまりません。型変数は型のセットなし数量詞(モノタイプと呼ばれます)に対してのみ数量化されるため、「述語的」と呼ばれます。 l : Nat
のplus
をc
にインスタンス化したため、Nat
の定義にも同様に非正規性が必要でした。
最後に、(システムFとは異なり)任意に再帰的な型を持つ言語であっても、非限定性と上位の多態性の両方が必要な最後の理由を1つ述べておきます。 Haskellには、「状態スレッドモナド」と呼ばれるエフェクト用のモナドがあります。アイデアは、状態スレッドモナドでは物事を変更できるが、結果が変更可能なものに依存しないようにエスケープする必要があるということです。これは、STの計算が明らかに純粋であることを意味します。この要件を適用するには、より高いランクの多態性を使用します
runST :: forall a. (forall s. ST s a) -> a
ここでは、a
がs
を導入するスコープの外にバインドされていることを確認することで、a
がs
に依存しない整形式を表していることがわかります。 s
を使用して、特定の状態スレッド内のすべての変更可能なものをパラメータ化します。これにより、a
は変更可能なものとは独立しているため、ST
の計算範囲から抜け出すものはありません。タイプを使用して不正な形式のプログラムを除外する素晴らしい例。
ちなみに、型理論について学ぶことに興味があるなら、良い本を一冊か二冊買うことをお勧めします。このことを少しずつ学ぶのは難しいです。一般的にPL理論(および型理論のいくつかの要素)に関するPierceまたはHarperの本の1つをお勧めします。 「型とプログラミング言語の高度なトピック」という本は、型理論のかなりの量をカバーしています。最後に、「マーティン・ロフの型理論におけるプログラミング」は、マーティン・ロフが概説した意図的な型理論への非常に優れた解説です。