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


20

F#のYコンビネーターの定義は

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

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

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

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

そう :

  • 一方では、Yコンビネータを使用するには、関数自体とは明示的に異なる継続が必要です。
  • TCOを適用する必要がある場合は、fで保留中の操作はなく、f自体のみを呼び出します。

これら2つを調整する方法を知っていますか?アキュムレータトリックを使用したYや、CPSトリックを使用したYなど?それともそれができる方法がないことを証明する議論?


yの実装にrecキーワークを追加しましたか?私は...それが私の読書からそれを必要と考える必要があります
ジミー・ホッファ

テールコールが最適化されていないという証拠はありますか?私は..コンパイラが何かを思い付くためにスマート十分であれば、私は驚かないだろう、あなたはその関数のILを読んで、見たいかもしれないと思うべきです
ジミー・ホッファ

まっすぐに結び付けられていない再帰の場合は、そうではありません。ただし、y呼び出しを介してスタックフレームが再利用されるという事実に応じて、このようなことを許可するように書き換えることができます。うん、ILを見る必要があるかもしれませんが、その経験はありません。
ニコラス

5
私はアカウントを作成し、ここでコメントするために50ポイントを得ました。この質問は本当に興味深いです。完全に依存すると思いますf。サンクでyテールコールfする可能性があることがわかり(y f)ますが、あなたが言うfように、保留中の操作があるかもしれません。テールコールにより適した別のコンビネーターがあるかどうかを知ることは興味深いと思います。この質問がCS Stackexchangeサイトでもっと注目されるのではないかと思いますか?
ジョンカートライト

回答:


4

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

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

Yコンビネータは理論的な構造であり、ラムダ計算を完全にチューリングするためにのみ必要です(ラムダ計算にはループがなく、ラムダには再帰に使用できる名前がありません)。

このように、Yコンビネーターは本当に魅力的です。

しかし、実際の再帰にYコンビネーターを実際に使用する人はいません!(それが本当に機能することを示すために、たぶん楽しみのためを除いて)

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

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


2
Yコンビネーターは、使用するたびに解け続ける結び目を結びつけるようなものです。ほとんどのシステムはこれを短縮し、メタレベルで結び目を結びます。そのため、結び付けられる必要はありません。
ダンD.

1
最後の段落については、基本的にグラフの縮小を使用して遅延評価を行うHaskellを検討してください。しかし、私のお気に入りは、完全な正規形への最小の縮小で常にチャーチ・ロッサー格子のパスをとる最適な縮小です。このように表示されているAspertiとGuerriniの関数型プログラミング言語のザ・最適な実装BOHM 1.1も参照してください。
ダンD.

@DanD。リンクのおかげで、後でポストスクリプト対応のブラウザで試してみます。もちろん、私にとって学ぶべきことがあります。しかし、コンパイルされた haskellはグラフの削減を行うと確信していますか?私はこれを疑います。
インゴ

1
実際には、グラフ削減を使用します。「GHCは、スパインレスタグレスGマシン(STG)にコンパイルします。これは、概念上のグラフ削減マシン(つまり、上記のグラフ削減を実行する仮想マシン)です。」From ... STGマシンの詳細については、Simon Peyton Jonesのストックハードウェアでの遅延関数型言語実装:Spineless Tagless G-machine」を参照してください
ダンD.

@DanD。あなたがリンクした同じ記事で、GHCは「最終的に実際のマシンコードにコンパイルする前に(おそらくCCCを使用してGCCを使用して)その表現に対して多くの最適化を行います」とさらに読みます。
インゴ

0

私はこの答えについて完全には確信していませんが、私が思いつくことができる最高のものです。

yコンビネータは本質的に怠け者です。厳密な言語では、余分なラムダを使用して遅延を手動で追加する必要があります。

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

定義は、終了するために怠を必要とするように見え(y f)ます。そうでない場合、引数は評価を終了せず、fそれを使用したかどうかを評価する必要があります。レイジーコンテキストでのTOCはより複雑で、さらにの結果は、(y f)アプリケーションを使用せずに関数合成を繰り返しますx。これがO(n)メモリを取る必要があるかどうかはわかりませんが、nは再帰の深さですが、(Haskellに切り替えると実際にはわからないので、可能な限り同じタイプのTOCを実現できるとは思いません。 F#)

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

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.