Yコンビネーターは、物事の「機能的」側面からのコンピューターサイエンスの概念です。ほとんどのプログラマーは、コンビネーターのことを知っていたとしても、コンビネーターについてはあまり知りません。
長い読み取りの準備ができている場合は、 マイクバニエにはgreat説明 があります。要するに、再帰をネイティブでサポートする必要のない言語で実装することができます。
Yコンビネーターは、関数内から関数を参照できない場合に再帰を有効にする「関数」(他の関数で動作する関数)です。コンピューターサイエンス理論では、再帰を一般化して実装を抽象化し、それによって問題の関数の実際の作業から分離します。再帰関数にコンパイル時の名前を必要としない利点は、一種のボーナスです。 =)
これは、 lambda functions をサポートする言語に適用できます。ラムダの expression -basedの性質は、通常、名前で自身を参照できないことを意味します。そして、変数を宣言し、それを参照し、それからラムダを割り当てて自己参照ループを完成させることにより、これを回避することは脆弱です。ラムダ変数をコピーして、元の変数を再割り当てすると、自己参照が壊れます。
Yコンビネータは、 static-typed 言語(多くの場合 プロシージャ言語 が多い)での実装が難しく、多くの場合、使用するのが面倒です。コンパイル時に既知の問題の関数。これは、使用する必要がある引数の数に対してyコンビネータを記述する必要があることを意味します。
以下は、C#でのY-Combinatorの使用方法と動作の例です。
Yコンビネータの使用には、再帰関数を作成する「異常な」方法が含まれます。まず、関数自体ではなく、既存の関数を呼び出すコードとして関数を作成する必要があります。
// Factorial, if func does the same thing as this bit of code...
x == 0 ? 1: x * func(x - 1);
次に、それを呼び出して関数を受け取る関数に変換し、呼び出しを行う関数を返します。これは関数と呼ばれます。1つの関数を受け取り、それを使用して別の関数を生成する操作を実行するためです。
// A function that creates a factorial, but only if you pass in
// a function that does what the inner function is doing.
Func<Func<Double, Double>, Func<Double, Double>> fact =
(recurs) =>
(x) =>
x == 0 ? 1 : x * recurs(x - 1);
これで、関数を受け取り、階乗のように見える別の関数を返す関数ができましたが、それ自体を呼び出す代わりに、外側の関数に渡された引数を呼び出します。これをどのように階乗にしますか?内部関数をそれ自体に渡します。 Y-Combinatorは、再帰を導入できる永続的な名前を持つ関数であることによってそれを行います。
// One-argument Y-Combinator.
public static Func<T, TResult> Y<T, TResult>(Func<Func<T, TResult>, Func<T, TResult>> F)
{
return
t => // A function that...
F( // Calls the factorial creator, passing in...
Y(F) // The result of this same Y-combinator function call...
// (Here is where the recursion is introduced.)
)
(t); // And passes the argument into the work function.
}
階乗がそれ自体を呼び出すのではなく、階乗が階乗ジェネレータを呼び出します(Y-Combinatorの再帰呼び出しによって返されます)。また、tの現在の値に応じて、ジェネレーターから返される関数は、t-1で再度ジェネレーターを呼び出すか、1を返すだけで再帰を終了します。
複雑で不可解ですが、実行時にすべて揺れ動き、その動作の鍵は「遅延実行」と、2つの関数にまたがる再帰の分割です。内側のFは引数として渡され、次の反復で呼び出されます。必要な場合のみ。
これを http://www.mail-archive.com/[email protected]/msg02716.html から解除しました。これは数年前に書いた説明です。
この例ではJavaScriptを使用しますが、他の多くの言語でも同様に機能します。
私たちの目標は、1変数の関数のみを使用して1変数の再帰関数を記述し、割り当てを行わないこと、名前で物事を定義することなどです(なぜこれが私たちの目標であるかは別の質問です。与えられた。)不可能のようですね。例として、階乗を実装しましょう。
さてステップ1は、少しだまされたら簡単にできると言うことです。 2つの変数と代入の関数を使用すると、少なくとも代入を使用して再帰を設定する必要がなくなります。
// Here's the function that we want to recurse.
X = function (recurse, n) {
if (0 == n)
return 1;
else
return n * recurse(recurse, n - 1);
};
// This will get X to recurse.
Y = function (builder, n) {
return builder(builder, n);
};
// Here it is in action.
Y(
X,
5
);
では、チートを減らすことができるかどうかを見てみましょう。まず、割り当てを使用していますが、必要はありません。 XとYをインラインで記述できます。
// No assignment this time.
function (builder, n) {
return builder(builder, n);
}(
function (recurse, n) {
if (0 == n)
return 1;
else
return n * recurse(recurse, n - 1);
},
5
);
しかし、2変数の関数を使用して、1変数の関数を取得しています。修正できますか? Haskell Curryという名の賢い人には、巧妙なトリックがあります。高次の関数が優れている場合、必要な関数は1変数のみです。証拠は、次のような純粋に機械的なテキスト変換で、2(または一般的な場合はそれ以上)変数の関数から1変数に取得できることです。
// Original
F = function (i, j) {
...
};
F(i,j);
// Transformed
F = function (i) { return function (j) {
...
}};
F(i)(j);
ここで...はまったく同じままです。 (このトリックは、発明者にちなんで「カリーイング」と呼ばれます。Haskell言語は、Haskell Curryからも名付けられています。役に立たない雑学のファイル。)この変換をどこにでも適用して、最終バージョンを取得します。
// The dreaded Y-combinator in action!
function (builder) { return function (n) {
return builder(builder)(n);
}}(
function (recurse) { return function (n) {
if (0 == n)
return 1;
else
return n * recurse(recurse)(n - 1);
}})(
5
);
試してみてください。戻り、それをボタンに結び付けるalert()このコードは、2つの変数の割り当て、宣言、または関数を使用せずに、階乗を再帰的に計算します。 (しかし、それがどのように機能するかをトレースしようとすると、頭が回る可能性があります。派生せずに渡すと、わずかに再フォーマットされただけでコードが混乱し混乱してしまいます。)
階乗を再帰的に定義する4行を、必要な他の再帰関数に置き換えることができます。
これを一から構築しようとすることに何か用途があるのだろうか。どれどれ。基本的な再帰的階乗関数を次に示します。
function factorial(n) {
return n == 0 ? 1 : n * factorial(n - 1);
}
計算自体を実行する代わりに、匿名の階乗計算関数を返すfact
という新しい関数をリファクタリングして作成しましょう。
function fact() {
return function(n) {
return n == 0 ? 1 : n * fact()(n - 1);
};
}
var factorial = fact();
それは少し奇妙ですが、それは何も悪いことではありません。各ステップで新しい階乗関数を生成しています。
この段階での再帰は、まだかなり明確です。 fact
関数は、それ自身の名前を認識する必要があります。再帰呼び出しをパラメーター化しましょう:
function fact(recurse) {
return function(n) {
return n == 0 ? 1 : n * recurse(n - 1);
};
}
function recurser(x) {
return fact(recurser)(x);
}
var factorial = fact(recurser);
それは素晴らしいことですが、recurser
はまだそれ自身の名前を知る必要があります。それもパラメータ化しましょう:
function recurser(f) {
return fact(function(x) {
return f(f)(x);
});
}
var factorial = recurser(recurser);
ここで、recurser(recurser)
を直接呼び出す代わりに、結果を返すラッパー関数を作成しましょう。
function Y() {
return (function(f) {
return f(f);
})(recurser);
}
var factorial = Y();
これでrecurser
の名前を完全に削除できます。これはYの内部関数の単なる引数であり、関数自体に置き換えることができます。
function Y() {
return (function(f) {
return f(f);
})(function(f) {
return fact(function(x) {
return f(f)(x);
});
});
}
var factorial = Y();
まだ参照されている唯一の外部名はfact
ですが、これも簡単にパラメーター化され、完全で汎用的なソリューションが作成されることは明らかです。
function Y(le) {
return (function(f) {
return f(f);
})(function(f) {
return le(function(x) {
return f(f)(x);
});
});
}
var factorial = Y(function(recurse) {
return function(n) {
return n == 0 ? 1 : n * recurse(n - 1);
};
});
上記の答えのほとんどは、Yコンビネータが何であるかを説明していますが、それが何であるかfor。
固定小数点コンビネータ は、 ラムダ計算 が チューリング完了 であることを示すために使用されます。これは、計算理論において非常に重要な結果であり、 関数型プログラミング の理論的基礎を提供します。
固定小数点コンビネーターの研究は、関数型プログラミングを本当に理解するのにも役立ちました。しかし、実際のプログラミングでそれらを使用したことは一度もありません。
JavaScript のyコンビネータ:
var Y = function(f) {
return (function(g) {
return g(g);
})(function(h) {
return function() {
return f(h(h)).apply(null, arguments);
};
});
};
var factorial = Y(function(recurse) {
return function(x) {
return x == 0 ? 1 : x * recurse(x-1);
};
});
factorial(5) // -> 120
Edit:コードを見ると多くのことを学びますが、これは背景なしで飲み込むのが少し難しいです-ごめんなさい。他の回答で提示された一般的な知識を使用して、何が起こっているのかを解き始めることができます。
Y関数は「yコンビネーター」です。 Yが使用されているvar factorial
行を見てみましょう。後で内部関数でも使用されるパラメーター(この例ではrecurse
)を持つ関数を渡すことに注意してください。パラメーター名は、基本的に内部関数の名前になり、再帰呼び出しを実行できるようになります(定義でrecurse()
を使用するため)。おもちゃ。
Yが魔法をどのように行うかの完全な説明については、 リンクされた記事 (私ではなく)をチェックアウトしてください。
関数型プログラミングに詳しくないプログラマーで、今から始めることを気にしないが、少し好奇心が強い:
Y Combinatorは、関数に名前を付けることはできませんが、引数として渡したり、戻り値として使用したり、他の関数内で定義したりできる状況で再帰を実装できる数式です。
関数を引数として自分自身に渡すことで機能するため、自分自身を呼び出すことができます。
これはラムダ計算の一部であり、実際には数学ですが、事実上プログラミング言語であり、コンピューターサイエンス、特に関数型プログラミングの基本です。
プログラミング言語では関数に名前を付ける傾向があるため、Y Combinatorの日々の実用的な価値は限られています。
警察のラインナップで特定する必要がある場合、次のようになります。
Y =λf。(λx.f(x x))(λx.f(x x))
通常、(λx.f (x x))
が繰り返されるため、それを見つけることができます。
λ
シンボルはギリシャ文字のラムダであり、ラムダ計算に名前を付けます。ラムダ計算がどのようなものかという理由で、多くの(λx.t)
スタイルの用語があります。
他の答えはこれにかなり簡潔な答えを提供しますが、1つの重要な事実はありません:この複雑な方法で実用的な言語で固定小数点コンビネータを実装する必要はありません。 is ")。これは重要な理論的概念ですが、実用的な価値はほとんどありません。
Y-CombinatorとFactorial関数のJavaScript実装を次に示します(Douglas Crockfordの記事から入手可能: http://javascript.crockford.com/little.html )。
function Y(le) {
return (function (f) {
return f(f);
}(function (f) {
return le(function (x) {
return f(f)(x);
});
}));
}
var factorial = Y(function (fac) {
return function (n) {
return n <= 2 ? n : n * fac(n - 1);
};
});
var number120 = factorial(5);
Yコンビネータは、フラックスコンデンサの別名です。
私は、ClojureとSchemeの両方でYコンビネーターに一種の「ばかガイド」を書いて、それを理解するのを助けました。彼らは「The Little Schemer」の素材の影響を受けています
スキームでは: https://Gist.github.com/z5h/238891
またはClojure: https://Gist.github.com/z5h/5102747
どちらのチュートリアルもコメントが散在するコードであるため、お気に入りのエディターに切り取って貼り付けることができます。
固定小数点コンビネーターは、定義により等価性を満たす高階関数fix
です。
forall f. fix f = f (fix f)
fix f
は、固定小数点方程式の解x
を表します
x = f x
自然数の階乗は、
fact 0 = 1
fact n = n * fact (n - 1)
fix
を使用すると、一般的/μ再帰関数の任意の構成的証明を、非自己参照なしで導出できます。
fact n = (fix fact') n
どこで
fact' rec n = if n == 0
then 1
else n * rec (n - 1)
そのような
fact 3
= (fix fact') 3
= fact' (fix fact') 3
= if 3 == 0 then 1 else 3 * (fix fact') (3 - 1)
= 3 * (fix fact') 2
= 3 * fact' (fix fact') 2
= 3 * if 2 == 0 then 1 else 2 * (fix fact') (2 - 1)
= 3 * 2 * (fix fact') 1
= 3 * 2 * fact' (fix fact') 1
= 3 * 2 * if 1 == 0 then 1 else 1 * (fix fact') (1 - 1)
= 3 * 2 * 1 * (fix fact') 0
= 3 * 2 * 1 * fact' (fix fact') 0
= 3 * 2 * 1 * if 0 == 0 then 1 else 0 * (fix fact') (0 - 1)
= 3 * 2 * 1 * 1
= 6
この正式な証明
fact 3 = 6
rewritesの固定小数点コンビネーター等価を系統的に使用します
fix fact' -> fact' (fix fact')
untyped lambda calculus形式は文脈自由文法で構成されています
E ::= v Variable
| λ v. E Abstraction
| E E Application
ここで、v
はbetaおよびeta reductionルールと一緒に変数にまたがります
(λ x. B) E -> B[x := E] Beta
λ x. E x -> E if x doesn’t occur free in E Eta
ベータ削減は、抽象化(「関数」)本体x
内の変数B
のすべてのフリーオカレンスを、式(「引数」)E
で置き換えます。 Etaの削減により、冗長な抽象化が排除されます。形式から省略されることもあります。 irreducible式は、削減規則が適用されず、normalにありますまたは正規形。
λ x y. E
略記です
λ x. λ y. E
(抽象多元性)、
E F G
略記です
(E F) G
(アプリケーションの左結合性)、
λ x. x
そして
λ y. y
alpha-equivalentです。
抽象化とアプリケーションは、ラムダ計算の2つの唯一の「言語プリミティブ」ですが、任意の複雑なデータと操作のencodingを許可します。
教会の数字は、ペアノ公理的自然に似た自然数のエンコードです。
0 = λ f x. x No application
1 = λ f x. f x One application
2 = λ f x. f (f x) Twofold
3 = λ f x. f (f (f x)) Threefold
. . .
SUCC = λ n f x. f (n f x) Successor
ADD = λ n m f x. n f (m f x) Addition
MULT = λ n m f x. n (m f) x Multiplication
. . .
その正式な証明
1 + 2 = 3
ベータ削減の書き換えルールを使用:
ADD 1 2
= (λ n m f x. n f (m f x)) (λ g y. g y) (λ h z. h (h z))
= (λ m f x. (λ g y. g y) f (m f x)) (λ h z. h (h z))
= (λ m f x. (λ y. f y) (m f x)) (λ h z. h (h z))
= (λ m f x. f (m f x)) (λ h z. h (h z))
= λ f x. f ((λ h z. h (h z)) f x)
= λ f x. f ((λ z. f (f z)) x)
= λ f x. f (f (f x)) Normal form
= 3
ラムダ計算では、combinatorsは自由変数を含まない抽象化です。最も単純なもの:I
、IDコンビネーター
λ x. x
恒等関数と同型
id x = x
このようなコンビネーターは、SKIシステムのようなコンビネーター計算のプリミティブ演算子です。
S = λ x y z. x z (y z)
K = λ x y. x
I = λ x. x
ベータ削減は強く正規化されていません;すべての還元可能な表現、「redexes」は、ベータ削減の下で正規形に収束するわけではありません。簡単な例は、omega ω
コンビネーターの分岐アプリケーションです
λ x. x x
それ自体に:
(λ x. x x) (λ y. y y)
= (λ y. y y) (λ y. y y)
. . .
= _|_ Bottom
左端の部分式(「ヘッド」)の削減が優先されます。 Applicative orderは置換前に引数を正規化しますが、normal orderは正規化しません。 2つの戦略は、熱心な評価に類似しています。 C、および遅延評価、たとえばハスケル。
K (I a) (ω ω)
= (λ k l. k) ((λ i. i) a) ((λ x. x x) (λ y. y y))
熱心なapplicative-orderベータ削減の下で分岐
= (λ k l. k) a ((λ x. x x) (λ y. y y))
= (λ l. a) ((λ x. x x) (λ y. y y))
= (λ l. a) ((λ y. y y) (λ y. y y))
. . .
= _|_
なぜならstrictセマンティクス
forall f. f _|_ = _|_
しかし、怠normalな標準次数のベータ削減の下で収束します
= (λ l. ((λ i. i) a)) ((λ x. x x) (λ y. y y))
= (λ l. a) ((λ x. x x) (λ y. y y))
= a
式に正規形がある場合、正規順序のベータ削減はそれを見つけます。
Y
の必須プロパティ固定小数点コンビネーター
λ f. (λ x. f (x x)) (λ x. f (x x))
によって与えられます
Y g
= (λ f. (λ x. f (x x)) (λ x. f (x x))) g
= (λ x. g (x x)) (λ x. g (x x)) = Y g
= g ((λ x. g (x x)) (λ x. g (x x))) = g (Y g)
= g (g ((λ x. g (x x)) (λ x. g (x x)))) = g (g (Y g))
. . . . . .
同等性
Y g = g (Y g)
に同型である
fix f = f (fix f)
型付けされていないラムダ計算は、一般的/μ-再帰関数上の任意の構成的証明をエンコードできます。
FACT = λ n. Y FACT' n
FACT' = λ rec n. if n == 0 then 1 else n * rec (n - 1)
FACT 3
= (λ n. Y FACT' n) 3
= Y FACT' 3
= FACT' (Y FACT') 3
= if 3 == 0 then 1 else 3 * (Y FACT') (3 - 1)
= 3 * (Y FACT') (3 - 1)
= 3 * FACT' (Y FACT') 2
= 3 * if 2 == 0 then 1 else 2 * (Y FACT') (2 - 1)
= 3 * 2 * (Y FACT') 1
= 3 * 2 * FACT' (Y FACT') 1
= 3 * 2 * if 1 == 0 then 1 else 1 * (Y FACT') (1 - 1)
= 3 * 2 * 1 * (Y FACT') 0
= 3 * 2 * 1 * FACT' (Y FACT') 0
= 3 * 2 * 1 * if 0 == 0 then 1 else 0 * (Y FACT') (0 - 1)
= 3 * 2 * 1 * 1
= 6
(乗算遅延、合流)
Churchianの型なしラムダ計算では、Y
の他に、再帰的に列挙可能な固定小数点コンビネーターの無限大が存在することが示されています。
X = λ f. (λ x. x x) (λ x. f (x x))
Y' = (λ x y. x y x) (λ y x. y (x y x))
Z = λ f. (λ x. f (λ v. x x v)) (λ x. f (λ v. x x v))
Θ = (λ x y. y (x x y)) (λ x y. y (x x y))
. . .
標準次数のベータ削減により、拡張されていない型付けされていないラムダ計算がチューリング完全な書き換えシステムになります。
Haskellでは、固定小数点コンビネーターをエレガントに実装できます
fix :: forall t. (t -> t) -> t
fix f = f (fix f)
Haskellの怠さは、すべての部分式が評価される前に有限に正規化されます。
primes :: Integral t => [t]
primes = sieve [2 ..]
where
sieve = fix (\ rec (p : ns) ->
p : rec [n | n <- ns
, n `rem` p /= 0])
Yコンビネーターは、匿名の再帰を実装します。代わりに
function fib( n ){ if( n<=1 ) return n; else return fib(n-1)+fib(n-2) }
できるよ
function ( fib, n ){ if( n<=1 ) return n; else return fib(n-1)+fib(n-2) }
もちろん、yコンビネーターは名前による呼び出し言語でのみ機能します。これを通常のcall-by-value言語で使用する場合は、関連するzコンビネーターが必要になります(yコンビネーターは発散/無限ループします)。
コンビネーターの初心者として、 Mike Vanierの記事 (Nicholas Mancusoに感謝)が本当に役立つことがわかりました。私の理解を文書化することに加えて、他の人たちの助けになるとしたら、私はとてもうれしい要約を書きたいと思います。
例として階乗を使用して、次のalmost-factorial
関数を使用して、数値x
の階乗を計算します。
def almost-factorial f x = if iszero x
then 1
else * x (f (- x 1))
上記の擬似コードでは、almost-factorial
は関数f
と数値x
を取ります(almost-factorial
はカリー化されているため、関数f
を取り込んで1アリティ関数を返すように見えます)。
almost-factorial
がx
の階乗を計算するとき、x - 1
の階乗の計算を関数f
に委任し、その結果をx
で累積します(この場合、(x-1)の結果にxを乗算します)。
almost-factorial
はcrappyバージョンの階乗関数(これは番号x - 1
までしか計算できない)を取り、less-crappy階乗のバージョン(番号x
まで計算します)。この形式のように:
almost-factorial crappy-f = less-crappy-f
階乗のless-crappyバージョンをalmost-factorial
に繰り返し渡すと、最終的に目的の階乗関数f
を取得します。次のように考えることができる場合:
almost-factorial f = f
almost-factorial f = f
がf
を意味するという事実は、関数almost-factorial
のfix-pointです。
これは、上記の関数の関係を見る非常に興味深い方法であり、私にとってはあっという間でした。 (まだ修正していない場合は、マイクの修正点に関する投稿をお読みください)
一般化するには、non-recursive関数fn
(ほぼ階乗のように)があり、そのfixがあります-pointfunction fr
(fと同様)、Y
は、Y
fn
を与えると、Y
がfn
の固定小数点関数を返します。
要約すると(fr
は1つのパラメーターのみを取ると仮定することで簡略化されています; x
は再帰的にx - 1
、x - 2
...に縮退します):
fn
として定義します:def fn fr x = ...accumulate x with result from (fr (- x 1))
、これはalmost-useful関数-fn
をx
で直接使用することはできませんが、すぐに役立ちます。この非再帰的なfn
は、関数fr
を使用して結果を計算しますfn fr = fr
、fr
はfn
の固定点、fr
はuseful関数、fr
でx
を使用して結果を取得できますY fn = fr
、Y
は関数の固定小数点を返し、Y
はほぼを返す-useful関数fn
intousefulfr
Y
の導出(含まれていません)Y
の派生をスキップして、Y
の理解に進みます。 Mike Vainerの投稿には多くの詳細があります。
Y
の形式Y
は次のように定義されます(lambda calculusformat):
Y f = λs.(f (s s)) λs.(f (s s))
関数の左側にある変数s
を置き換えると、
Y f = λs.(f (s s)) λs.(f (s s))
=> f (λs.(f (s s)) λs.(f (s s)))
=> f (Y f)
確かに、(Y f)
の結果はf
の固定小数点です。
(Y f)
が機能するのはなぜですか?f
のシグネチャに応じて、(Y f)
は任意のアリティの関数になります。簡略化するために、(Y f)
は階乗関数のように1つのパラメーターのみを取ると仮定します。
def fn fr x = accumulate x (fr (- x 1))
fn fr = fr
以降、継続します
=> accumulate x (fn fr (- x 1))
=> accumulate x (accumulate (- x 1) (fr (- x 2)))
=> accumulate x (accumulate (- x 1) (accumulate (- x 2) ... (fn fr 1)))
最も内側の(fn fr 1)
がベースケースであり、fn
がfr
を計算で使用しない場合、再帰的計算は終了します。
再びY
を見る:
fr = Y fn = λs.(fn (s s)) λs.(fn (s s))
=> fn (λs.(fn (s s)) λs.(fn (s s)))
そう
fr x = Y fn x = fn (λs.(fn (s s)) λs.(fn (s s))) x
私にとって、このセットアップの魔法の部分は次のとおりです。
fn
とfr
は相互に依存しています:fr
は内部で「ラップ」fn
、fr
はx
の計算に使用されるたびに、fn
を「生成」(「リフト」?)し、fn
に計算を委任し、fr
をx
に渡します);一方、fn
はfr
に依存し、fr
を使用して、より小さい問題x-1
の結果を計算します。fr
を使用してfn
を定義するとき(fn
がその操作でfr
を使用するとき)、実際のfr
はまだ定義されていません。fn
です。 fn
に基づいて、Y
はfr
(特定の形式のヘルパー関数)を作成し、recursive方式でfn
の計算を容易にします。現時点ではY
をこのように理解するのに役立ちました。
ところで、私も本を見つけました ラムダ計算による関数型プログラミング入門 非常に良いです、私はそれを手伝うだけです、そして本の中でY
の周りに頭を出すことができなかったという事実この投稿へ。
Nicholas Mancusoによる回答 で言及されている 記事 (これは読む価値のあるTOTALY)からコンパイルされた 元の質問 に対する回答です。他の回答として:
Yコンビネーターとは何ですか?
Yコンビネータは、「関数」(または高次関数-他の関数で動作する関数)であり、単一の引数を取ります。これは、再帰的ではない関数で、次の関数のバージョンを返します。再帰的。
やや再帰的=)、しかしより詳細な定義:
コンビネーター—自由変数のない単なるラムダ式です。
自由変数—バインドされた変数ではない変数です。
バウンド変数—その変数名を引数の1つとして持つラムダ式の本体内に含まれる変数。
これについて考える別の方法は、コンビネーターがそのようなラムダ式であり、コンビネーターの名前を、見つかったすべての場所でその定義で置き換えることができ、すべてがまだ機能していることです(コンビネーターが無限ループに入る場合ラムダ本体内に自分自身への参照を含む)。
Yコンビネーターは、固定小数点コンビネーターです。
関数の固定小数点は、関数によってそれ自体にマッピングされる関数のドメインの要素です。
つまり、c
はf(x)
の場合、関数f(c) = c
の固定小数点です。
これはf(f(...f(c)...)) = fn(c) = c
を意味します
コンビネータはどのように機能しますか?
以下の例では、strong + dynamic入力と仮定しています:
Lazy(normal-order)Y-combinator:
この定義は、遅延(また、遅延、必要に応じて呼び出す)評価のある言語に適用されます。評価戦略は、その値が必要になるまで式の評価を遅らせます。
Y = λf.(λx.f(x x)) (λx.f(x x)) = λf.(λx.(x x)) (λx.f(x x))
つまり、特定の関数f
(非再帰関数)に対して、最初にλx.f(x x)
を計算し、このラムダ式をそれ自体に適用することで、対応する再帰関数を取得できます。
Strict(applicative-order)Y-combinator:
この定義は厳密な(また、熱心、貪欲)評価の言語に適用されます—変数にバインドされるとすぐに式が評価される評価戦略です。
Y = λf.(λx.f(λy.((x x) y))) (λx.f(λy.((x x) y))) = λf.(λx.(x x)) (λx.f(λy.((x x) y)))
それは本質的に怠laなものと同じですが、ラムダの本体評価を遅らせるための余分なλ
ラッパーがあります。私は 別の質問 を尋ねましたが、このトピックに多少関連しています。
彼らは何のために良いですか?
盗まれた Chris Ammermanによる回答からの借用:Yコンビネーターは再帰を一般化し、実装を抽象化し、それによって問題の関数の実際の作業から分離します。
Yコンビネーターにはいくつかの実用的な用途がありますが、それは主に理論的な概念であり、それを理解することで全体的なビジョンが広がり、分析および開発者のスキルが向上する可能性があります。
それらは手続き型言語で有用ですか?
As Mike Vanierが述べた :多くの静的型付け言語でY Combinatorを定義することは可能ですが、(少なくとも私が見た例では)そのような定義には通常Y Combinator自体には単純な静的型がないため、いくつかの非自明な型ハッカーです。これはこの記事の範囲を超えているため、これ以上言及しません。
そして Chris Ammermanによる :ほとんどの手続き型言語には静的型付けがあります。
それで、これに答えてください。そうではありません。
This-operatorはあなたの人生を単純化できます:
var Y = function(f) {
return (function(g) {
return g(g);
})(function(h) {
return function() {
return f.apply(h(h), arguments);
};
});
};
次に、余分な機能を回避します。
var fac = Y(function(n) {
return n == 0 ? 1 : n * this(n - 1);
});
最後に、fac(5)
を呼び出します。
固定小数点コンビネーター(または固定小数点演算子)は、他の関数の固定小数点を計算する高次関数です。この操作は、言語のランタイムエンジンからの明示的なサポートなしで、書き換えルールの形式で再帰を実装できるため、プログラミング言語の理論に関連しています。 (srcウィキペディア)
これに答える最良の方法は、JavaScriptのような言語を選択することだと思います。
function factorial(num)
{
// If the number is less than 0, reject it.
if (num < 0) {
return -1;
}
// If the number is 0, its factorial is 1.
else if (num == 0) {
return 1;
}
// Otherwise, call this recursive procedure again.
else {
return (num * factorial(num - 1));
}
}
次に、関数内で関数の名前を使用しないように書き換えますが、それでも再帰的に呼び出します。
関数名factorial
が表示される唯一の場所は、呼び出しサイトです。
ヒント:関数の名前は使用できませんが、パラメーターの名前は使用できます。
問題を解決します。調べないでください。解決したら、yコンビネータが解決する問題を理解できます。