多形型 `forall t:Type、t-> t`をもつ関数が恒等関数でなければならないのはなぜですか?


18

プログラミング言語理論は初めてです。私は、インストラクターがポリモーフィック型の関数がforall t: Type, t->tアイデンティティであると主張するオンライン講義をいくつか見ていましたが、その理由は説明しませんでした。誰かが私に理由を説明できますか?第一原理からの主張の証拠かもしれません。


3
この質問は重複しているに違いないと思ったが、見つけられない。cs.stackexchange.com/questions/341/…は一種のフォローアップです。標準参照は無料定理です!フィル・ワドラー。
ジル「SO-悪であるのをやめる」

1
他のことを行うこのタイプの汎用関数を構築してみてください。あなたは何もないことがわかります。
ベルギ

@Bergiはい、私は反例を見つけることができませんでしたが、それを証明する方法がまだわかりませんでした。
abhishek

しかし、それを見つけようとしたときの観察は何でしたか?なぜあなたの試みはうまくいかなかったのですか?
ベルギ

@Gillesたぶんcs.stackexchange.com/q/19430/14663を覚えていますか?
ベルギ

回答:


32

最初に注意することは、これは必ずしも真実ではないということです。たとえば、言語に応じて、そのタイプの関数は、アイデンティティ関数であることに加えて、1)永久にループする、2)状態を変更する、3)リターンするnull、4)例外をスローする、5)何らかのI / Oを実行する、 6)スレッドをフォークして他のことをする、7)call/ccシェナンガンを行う、8)Javaのようなものを使用するObject.hashCode、9)リフレクションを使用して型が整数であるかどうかを判別し、ある場合はインクリメントする、10)リフレクションを使用して呼び出しスタックを分析するそれが呼び出されるコンテキストに基づいて何かをする、11)おそらく他の多くのこと、そして確かに上記の任意の組み合わせ。

したがって、これにつながる特性であるパラメトリック性は、言語全体の特性であり、言語にはより強いバリエーションとより弱いバリエーションがあります。型理論で研究された多くの形式計算では、上記の動作は発生しません。たとえば、パラメータFが最初に研究されたシステムF /純粋な多相ラムダ計算では、上記の動作は発生しません。それは単に、例外、可変状態、持っていないnullcall/cc、I / O、反射を、そして、それは強く、それが永遠にループすることはできませんので、正規ます。ジルがコメントで言及したように、論文定理は無料です!Phil Wadlerによるこのトピックへの良い入門であり、その参照は理論、特に論理関係の手法にさらに入ります。このリンクには、Wadlerによるパラメトリック性のトピックに関する他の論文もいくつかリストされています。

パラメトリック性は言語の特性であるため、それを証明するには、まず言語を正式に明確にし、次に比較的複雑な議論をする必要があります。ポリモーフィックラムダ計算にあると仮定したこの特定の場合の非公式の引数はt、入力に対して操作を実行できないことを何も知らないためです(たとえば、またはそのタイプの値を作成します(私たちが知っているすべての場合、t= Void、値のないタイプ)。typeの値を生成する唯一の方法tは、与えられた値を返すことです。他の動作は不可能です。これを確認する1つの方法は、強力な正規化を使用して、このタイプの正規形用語が1つしかないことを示すことです。


1
システムFは、型システムが検出できない無限ループをどのように回避しましたか?これは一般的なケースでは解決不能として分類されます。
ジョシュア

2
@Joshua-停止問題の標準的な不可能性の証明は、最初に無限ループがあるという仮定から始まります。したがって、System Fに無限ループがない理由を疑問視することは、循環推論です。さらに広く言えば、システムFはチューリング完全に近いわけではないので、その証明の仮定のいずれかを満たしているとは思えません。コンピューターがすべてのプログラムが終了することを証明するのに十分なほど簡単です(再帰なし、whileループなし、ループに対して非常に弱いなど)。
ジョナサンキャスト

@Joshua:一般的なケースでは解決できませんが、多くの特別なケースで解決することを妨げません。特に、よく型付けされたシステムF用語であるプログラムはすべて停止することが証明されています。これらすべてのプログラムで機能する1つの統一された証拠があります。明らかに、これは他のプログラムが存在することを意味し、システムFでは入力できません
...-cody

15

主張の証拠は非常に複雑ですが、それが本当に必要なものである場合は、トピックに関するレイノルズの元の論文をチェックできます。

重要な考え方は、パラメータのポリモーフィック関数を保持することです。ここで、ポリモーフィック関数の本体は、関数のすべてのモノモーフィックなインスタンス化で同じです。そのようなシステムでは、ポリモーフィック型のパラメーターの型について仮定することはできません。また、スコープ内の唯一の値がジェネリック型を持っている場合、それとは何の関係もありませんが、それを返すか、他の関数に渡します。 veを定義します。これは、結果を返すか渡すだけです。したがって、最後にできることは、パラメーターを返す前に一連のID関数を実行することだけです。


8

デレクが言及しているすべての警告と、集合論の使用に起因するパラドックスを無視して、レイノルズ/ワドラーの精神の証拠をスケッチさせてください。

タイプの関数:

f :: forall t . t -> t

ftt

ポリモフィック関数を正式に定義するには、型を値のセットとしてではなく、関係として扱う必要があるという考え方です。Int等値関係を誘導するなどの基本型。たとえば、2つのInt値が等しい場合、それらは関連付けられます。関数は、関連する値を関連する値にマップする場合に関連します。興味深いケースは多相関数です。関連するタイプを関連する値にマップします。

fg

forall t . t -> t

stfsfssstgtttfgfsgt

fstfsft

()()t()t((), c)ctf()ftf()()()ftcc()cftidttfid

詳細については、私のブログをご覧ください。


-2

編集:上記のコメントは、不足している部分を提供しています。一部の人々は、チューリング完全ではない言語で意図的に遊んでいます。私はそのような言語を特に気にしません。本当に使いやすい非チューリング完全言語は、設計するのが非常に難しいものです。これ以降は、これらの定理を完全な言語に適用しようとするとどうなるかを詳しく説明します。

偽!

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つの変数を比較します。つまり、同じ値が含まれています。同等の値ではなく、同じ値。関数fgは定義によって同等ですが、同じではありません。

この関数自体が渡された場合、他の何かを返します。それ以外の場合は、入力を返します。他のものはそれ自体と同じ型を持っているので、それを置き換えることができます。言い換えると、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]¹

型推論をより簡単に処理できると仮定できると仮定したい場合。

定理が成立するまでドメインを制限しようとすると、最終的にそれを非常に制限しなければなりません。

  • 純粋な機能(可変状態、IOなし)。OK 多くの時間、関数に対して証明を実行したい。
  • 空の標準ライブラリ。あー
  • いいえ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]

コンパイルしてはいけません。基本的な問題は、実行時配列のインデックス作成が機能しなくなることです。


@Bergi:反例を作成しました。
ジョシュア

1
あなたの答えと他の2つとの違いについて少し考えてみてください。デレクの冒頭の文は「最初に注意することは、これは必ずしも真実ではないということです」。そして、彼言語のどの特性がそれを実現するかを説明します。jmiteはまた、それが本当である理由を説明します。対照的に、あなたの答えは、説明のない不特定の(そして一般的でない言語の)例です。(foilとにかく数量詞とは何ですか?)これはまったく役に立ちません。
ジル「SO-悪であるのをやめる」

1
@DW:aがfの場合、aのタイプはfのタイプであり、これもgのタイプであるため、typecheckはパスする必要があります。実際のコンパイラがそれを追い出した場合、静的型システムが間違っているために実際の言語が常に持っているランタイムキャストを使用し、実行時に失敗することはありません。
ジョシュア

2
それは静的型チェッカーの仕組みではありません。単一の特定の入力に対してタイプが一致するかどうかはチェックしません。特定のタイプルールがあります。これは、可能なすべての入力に対して関数がタイプチェックすることを保証することを目的としています。タイプキャストの使用が必要な場合、このソリューションはそれほど面白くありません。もちろん、型システムをバイパスした場合、関数の型は何も保証しません。
DW

1
@DW:ポイントを見逃しています。静的型チェッカーには、コードがそれを見つけるウィットを持っている場合、コードが型安全であることを証明するのに十分な情報があります。
ジョシュア
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.