プログラミング言語理論は初めてです。私は、インストラクターがポリモーフィック型の関数がforall t: Type, t->t
アイデンティティであると主張するオンライン講義をいくつか見ていましたが、その理由は説明しませんでした。誰かが私に理由を説明できますか?第一原理からの主張の証拠かもしれません。
プログラミング言語理論は初めてです。私は、インストラクターがポリモーフィック型の関数がforall t: Type, t->t
アイデンティティであると主張するオンライン講義をいくつか見ていましたが、その理由は説明しませんでした。誰かが私に理由を説明できますか?第一原理からの主張の証拠かもしれません。
回答:
最初に注意することは、これは必ずしも真実ではないということです。たとえば、言語に応じて、そのタイプの関数は、アイデンティティ関数であることに加えて、1)永久にループする、2)状態を変更する、3)リターンするnull
、4)例外をスローする、5)何らかのI / Oを実行する、 6)スレッドをフォークして他のことをする、7)call/cc
シェナンガンを行う、8)Javaのようなものを使用するObject.hashCode
、9)リフレクションを使用して型が整数であるかどうかを判別し、ある場合はインクリメントする、10)リフレクションを使用して呼び出しスタックを分析するそれが呼び出されるコンテキストに基づいて何かをする、11)おそらく他の多くのこと、そして確かに上記の任意の組み合わせ。
したがって、これにつながる特性であるパラメトリック性は、言語全体の特性であり、言語にはより強いバリエーションとより弱いバリエーションがあります。型理論で研究された多くの形式計算では、上記の動作は発生しません。たとえば、パラメータFが最初に研究されたシステムF /純粋な多相ラムダ計算では、上記の動作は発生しません。それは単に、例外、可変状態、持っていないnull
、call/cc
、I / O、反射を、そして、それは強く、それが永遠にループすることはできませんので、正規ます。ジルがコメントで言及したように、論文定理は無料です!Phil Wadlerによるこのトピックへの良い入門であり、その参照は理論、特に論理関係の手法にさらに入ります。このリンクには、Wadlerによるパラメトリック性のトピックに関する他の論文もいくつかリストされています。
パラメトリック性は言語の特性であるため、それを証明するには、まず言語を正式に明確にし、次に比較的複雑な議論をする必要があります。ポリモーフィックラムダ計算にあると仮定したこの特定の場合の非公式の引数はt
、入力に対して操作を実行できないことを何も知らないためです(たとえば、またはそのタイプの値を作成します(私たちが知っているすべての場合、t
= Void
、値のないタイプ)。typeの値を生成する唯一の方法t
は、与えられた値を返すことです。他の動作は不可能です。これを確認する1つの方法は、強力な正規化を使用して、このタイプの正規形用語が1つしかないことを示すことです。
主張の証拠は非常に複雑ですが、それが本当に必要なものである場合は、トピックに関するレイノルズの元の論文をチェックできます。
重要な考え方は、パラメータのポリモーフィック関数を保持することです。ここで、ポリモーフィック関数の本体は、関数のすべてのモノモーフィックなインスタンス化で同じです。そのようなシステムでは、ポリモーフィック型のパラメーターの型について仮定することはできません。また、スコープ内の唯一の値がジェネリック型を持っている場合、それとは何の関係もありませんが、それを返すか、他の関数に渡します。 veを定義します。これは、結果を返すか渡すだけです。したがって、最後にできることは、パラメーターを返す前に一連のID関数を実行することだけです。
デレクが言及しているすべての警告と、集合論の使用に起因するパラドックスを無視して、レイノルズ/ワドラーの精神の証拠をスケッチさせてください。
タイプの関数:
f :: forall t . t -> t
ポリモフィック関数を正式に定義するには、型を値のセットとしてではなく、関係として扱う必要があるという考え方です。Int
等値関係を誘導するなどの基本型。たとえば、2つのInt
値が等しい場合、それらは関連付けられます。関数は、関連する値を関連する値にマップする場合に関連します。興味深いケースは多相関数です。関連するタイプを関連する値にマップします。
forall t . t -> t
f
s
t
()
()
t
()
t
((), c)
c
t
()
()
c
c
()
c
t
f
id
詳細については、私のブログをご覧ください。
編集:上記のコメントは、不足している部分を提供しています。一部の人々は、チューリング完全ではない言語で意図的に遊んでいます。私はそのような言語を特に気にしません。本当に使いやすい非チューリング完全言語は、設計するのが非常に難しいものです。これ以降は、これらの定理を完全な言語に適用しようとするとどうなるかを詳しく説明します。
偽!
function f(a): forall t: Type, t->t
function g(a): forall t: Type, t->t
return (a is g) ? f : a
return a is f ? g : a
ここで、is
演算子は参照IDの2つの変数を比較します。つまり、同じ値が含まれています。同等の値ではなく、同じ値。関数f
とg
は定義によって同等ですが、同じではありません。
この関数自体が渡された場合、他の何かを返します。それ以外の場合は、入力を返します。他のものはそれ自体と同じ型を持っているので、それを置き換えることができます。言い換えると、f
はをf(f)
返すため、アイデンティティではありませんg
が、アイデンティティはを返しf
ます。
定理が成立するためには、それを減らすとんでもない能力を仮定する必要があります
function cantor(n, <z, a>) : forall t: t: Type int, <int, t> -> <int, t>
return n > 1 ? cantor((n % 2 > 0) ? (n + 1) : n / 2, <z + 1, a>) : <z, a>
return cantor(1000, <0, a>)[1]¹
型推論をより簡単に処理できると仮定できると仮定したい場合。
定理が成立するまでドメインを制限しようとすると、最終的にそれを非常に制限しなければなりません。
raise
、いいえexit
。今、私たちは制約を受け始めています。nil
。これが問題になり始めています。1/0に対処する方法を使い果たしました。³最後の2つの制約の両方が存在するため、言語が機能しなくなりました。チューリングはまだ完了していますが、汎用作業を行うための唯一の方法は、要件が緩い言語を解釈する内部プラットフォームをシミュレートすることです。
¹コンパイラがそれを推測できると思うなら、これを試してください
function fermat(z) : int -> int
function pow(x, p)
return p = 0 ? 1 : x * pow(x, p - 1)
function f2(x, y, z) : int, int, int -> <int, int>
left = pow(x, 5) + pow(y, 5)
right = pow(z, 5)
return left = right
? <x, y>
: pow(x, 5) < right
? f2(x + 1, y, z)
: pow(y, 5) < right
? f2(2, y + 1, z)
: f2(2, 2, z + 1)
return f2(2, 2, z)
function cantor(n, <z, a>) : forall t: t: Type int, <int, t> -> <int, t>
return n > 1 ? cantor((n % 2 > 0) ? (n + 1) : n / 2, <z + 1, a>) : <z, a>
return cantor(fermat(3)[0], <0, a>)[1]
²コンパイラーがこれを行えないという証拠は、盲検化に依存します。複数のライブラリを使用して、コンパイラがループを一度に認識できないようにすることができます。また、プログラムは動作するが、コンパイラが利用可能なメモリで誘導を実行できないため、コンパイルできなかった場所にいつでも構築できます。
³誰かが、任意のジェネリック型がnilを返さなくても、このnilを返すことができると考えています。これは厄介なペナルティを支払うが、そのための効果的な言語は見られない。
function f(a, b, c): t: Type: t[],int,int->t
return a[b/c]
コンパイルしてはいけません。基本的な問題は、実行時配列のインデックス作成が機能しなくなることです。
foil
とにかく数量詞とは何ですか?)これはまったく役に立ちません。