web-dev-qa-db-ja.com

型チェックと再帰型(Haskell / OcamlでのY Combinatorの記述)

HaskellのコンテキストでY Combinatorを説明する場合、通常、単純な実装では、再帰的な型のため、Haskellで型チェックを行わないことに注意してください。

たとえば、 Rosettacode から:

The obvious definition of the Y Combinator in Haskell canot be used
because it contains an infinite recursive type (a = a -> b). Defining
a data type (Mu) allows this recursion to be broken.

newtype Mu a = Roll { unroll :: Mu a -> a }

fix :: (a -> a) -> a
fix = \f -> (\x -> f (unroll x x)) $ Roll (\x -> f (unroll x x))

そして確かに、「明白な」定義は型チェックを行いません。

λ> let fix f g = (\x -> \a -> f (x x) a) (\x -> \a -> f (x x) a) g

<interactive>:10:33:
    Occurs check: cannot construct the infinite type:
      t2 = t2 -> t0 -> t1
    Expected type: t2 -> t0 -> t1
      Actual type: (t2 -> t0 -> t1) -> t0 -> t1
    In the first argument of `x', namely `x'
    In the first argument of `f', namely `(x x)'
    In the expression: f (x x) a

<interactive>:10:57:
    Occurs check: cannot construct the infinite type:
      t2 = t2 -> t0 -> t1
    In the first argument of `x', namely `x'
    In the first argument of `f', namely `(x x)'
    In the expression: f (x x) a
(0.01 secs, 1033328 bytes)

Ocamlにも同じ制限があります。

utop # let fix f g = (fun x a -> f (x x) a) (fun x a -> f (x x) a) g;;
Error: This expression has type 'a -> 'b but an expression was expected of type 'a                                    
       The type variable 'a occurs inside 'a -> 'b

ただし、Ocamlでは、-rectypesスイッチを渡して再帰型を許可できます。

   -rectypes
          Allow  arbitrary  recursive  types  during type-checking.  By default, only recursive
          types where the recursion goes through an object type are supported.

-rectypesを使用すると、すべてが機能します。

utop # let fix f g = (fun x a -> f (x x) a) (fun x a -> f (x x) a) g;;
val fix : (('a -> 'b) -> 'a -> 'b) -> 'a -> 'b = <fun>
utop # let fact_improver partial n = if n = 0 then 1 else n*partial (n-1);;
val fact_improver : (int -> int) -> int -> int = <fun>
utop # (fix fact_improver) 5;;
- : int = 120

型システムと型推論に興味があるので、これはまだ答えられないいくつかの質問を引き起こします。

  • まず、型チェッカーはどのようにt2 = t2 -> t0 -> t1型を考え出すのですか?その型を思いついたら、問題は型(t2)が右側でそれ自体を参照していることでしょうか?
  • 次に、おそらく最も興味深いのは、Haskell/Ocaml型システムがこれを許可しない理由は何ですか? is十分な理由があると思いますが、Ocamlはデフォルトでcan-rectypesスイッチが指定されている場合、再帰型を処理します。

これらが本当に大きなトピックである場合は、関連する文献へのポインタをいただければ幸いです。

21
beta

まず、GHCエラー、

GHCはxを使用していくつかの制約を統合しようとしています。最初に、それを関数として使用します。

_x :: a -> b
_

次に、それをその関数の値として使用します

_x :: a
_

最後に、元の引数式と統合します。

_x :: (a -> b) -> c -> d
_

現在_x x_は_t2 -> t1 -> t0_を統合する試みになりますが、xの最初の引数である_t2_をxと統合する必要があるため、これを統合することはできません。したがって、エラーメッセージです。

次に、なぜ一般的な再帰型ではないのか。注目に値する最初のポイントは、equiとisoの再帰型の違いです。

  • 等再帰は、_mu X . Type_が任意に展開または折りたたむこととまったく同じです。
  • iso-recursive型は、型の再帰的な定義を折りたたんだり展開したりするfoldおよびunfoldの演算子のペアを提供します。

現在、等再帰型は理想的に聞こえますが、複雑な型システムで正しく処理するのはとてつもなく難しいです。それは実際に型チェックを決定不可能にすることができます。私はOCamlの型システムのすべての詳細に精通していませんが、Haskellの完全に再帰的な型により、型チェッカーが任意に型を統一しようとしてループする可能性があります。デフォルトでは、Haskellは型チェックが確実に終了するようにします。さらに、Haskellでは、型の同義語はばかげています。最も有用な再帰型はtype T = T -> ()のように定義されますが、Haskellではほとんどすぐにインライン化されますが、再帰型をインライン化することはできません。したがって、Haskellの再帰型では、類義語の処理方法を大幅に見直し、おそらく言語拡張として追加するだけの価値はありません。

Iso再帰型は使用するのが少し面倒です。型チェッカーに型を折りたたんだり展開したりする方法を型チェッカーに明示的に伝えなければならず、プログラムの読み取りと書き込みがより複雑になります。

ただし、これは、Mu型で行うことと非常によく似ています。 Rollは折りたたまれ、unrollは展開されます。したがって、実際には、等再帰型が組み込まれています。ただし、等再帰型は非常に複雑であるため、OCamlやHaskellなどのシステムでは、型レベルのフィックスポイントを介して繰り返しを渡す必要があります。

これに興味があるなら、タイプとプログラミング言語をお勧めします。私は正しい用語を持っていることを確認するためにこれを書いているので、私のコピーは私の膝で開いたままです:)

16
Daniel Gratzer

OCamlでは、-rectypesをパラメーターとしてコンパイラーに渡す(またはトップレベルで#rectypes;;と入力する)必要があります。大まかに言えば、これは統合中に「発生チェック」をオフにします。状況The type variable 'a occurs inside 'a -> 'bは問題ではなくなります。型システムは「正しい」(サウンドなど)ままです。型として時々発生する無限ツリーは「有理ツリー」と呼ばれます。型システムは弱くなります。つまり、一部のプログラマーエラーを検出できなくなります。

OCamlの例を使用した固定小数点演算子の詳細については、my ラムダ計算の講義 (スライド27以降)を参照してください。

2
lukstafi