web-dev-qa-db-ja.com

Yコンビネーターとテールコールの最適化

F#でのY Combinatorの定義は次のとおりです。

let rec y f x = f (y f) x

fは、最初の引数として、再帰的な副問題の継続があることを期待しています。 y fを継続として使用すると、開発できるようにfが連続する呼び出しに適用されることがわかります

let y f x = f (y f) x = f (f (y f)) x = f (f (f (y f))) x etc...

問題は、先験的に、このスキームが末尾呼び出しの最適化を使用できないことです。実際、fで保留中の操作がある可能性があります。この場合、fに関連付けられたローカルスタックフレームを単に変更することはできません。

そう :

  • 一方では、Y Combinatorを使用するには、関数自体よりも明示的なdifferentの継続が必要です。
  • tCOを適用する場合は、fで保留中の操作はなく、f自体のみを呼び出します。

これらの2つを調整する方法を知っていますか?アキュムレータトリックを持つY、またはCPSトリックを持つYのように?または、それを行う方法がないことを証明する議論?

20
nicolas

これら2つを調整する方法を知っていますか?

いいえ、そして正当な理由で、私見です。

Y-combinatorは理論上の構成要素であり、ラムダ計算を完全に処理するためにのみ必要です(ラムダ計算にはループがなく、ラムダには再帰に使用できる名前がないことに注意してください)。

このように、Y Combinatorは本当に魅力的です。

しかし:実際にはだれも使用実際の再帰のためのY結合子! (多分楽しみのためを除いて、それが本当に機能することを示すために。)

末尾呼び出しの最適化であるOTOHは、その名前が示すように、最適化です。それは言語の表現力に何も追加しません。それは私たちが気にかけているのは、スタック空間や再帰的コードのパフォーマンスなどの実用的な考慮事項のためだけです。

だからあなたの質問は次のようなものです:ベータ削減のためのハードウェアサポートはありますか? (ベータ削減とは、ラムダ式を削減する方法です。)しかし、関数型言語(私が知る限り)は、実行時にベータ削減されるラムダ式の表現にソースコードをコンパイルしません。

4
Ingo

この答えについては完全にはわかりませんが、私が思いつく最高の答えです。

Y Combinatorは本質的に怠惰です。厳密な言語では、遅延は手動で追加のラムダを介して追加する必要があります。

let rec y f x = f (y f) x

定義は終了するために遅延が必要であるように見えます。そうでない場合、(y f)引数は評価を終了せず、fがそれを使用したかどうかを評価する必要があります。レイジーコンテキストでのTOCはより複雑であり、さらに(y f)の結果は、xを適用せずに繰り返し関数合成されます。これにO(n) memoryが必要かどうかはわかりませんが、nは再帰の深さです)(switching with私は実際にはF#を知らないのでHaskellに)

length acc []    = acc
length acc (a:b) = length (acc+1) b 

まだ気付いていない場合は、Haskellでのfoldlfoldl'の違いが状況に光を当てるかもしれません。 foldlは、熱心な言語で行われるように書かれています。しかし、TOCされる代わりに、アキュムレータは部分的に評価できない潜在的に巨大なサンクを格納するため、実際にはfoldrよりも悪いです。 (これは、foldlとfoldl 'の両方が無限リストで機能しない理由に関連しています。)したがって、Haskellのより最近のバージョンでは、foldl'が追加され、関数が繰り返されるたびにアキュムレータの評価を強制して、巨大でないことを確認します。サンクが作成されます。きっと http://www.haskell.org/haskellwiki/Foldr_Foldl_Foldl%27 私よりもこのことをよく説明できるでしょう。

0
Ericson2314