web-dev-qa-db-fra.com

Qu'est-ce qu'un combinateur Y?

Un Y-combinator est un concept informatique du côté "fonctionnel" des choses. La plupart des programmeurs n'en savent pas beaucoup sur les combinateurs, s'ils en ont même entendu parler.

  • Qu'est-ce qu'un combinateur Y?
  • Comment fonctionnent les combinateurs?
  • À quoi servent-ils?
  • Sont-ils utiles dans les langages procéduraux?
372
Chris Ammerman

Si vous êtes prêt pour une longue lecture, Mike Vanier a une excellente bonne explication . En bref, il vous permet d’implémenter la récursivité dans un langage qui ne la prend pas nécessairement en charge de manière native.

194
Nicholas Mancuso

Un Y-combinator est une "fonctionnelle" (une fonction qui opère sur d'autres fonctions) qui permet la récursivité, lorsque vous ne pouvez pas vous référer à la fonction de l'intérieur. Dans la théorie informatique, il généralise la récursion , en résumant sa mise en œuvre, le séparant ainsi du travail réel de la fonction en question. L'avantage de ne pas avoir besoin d'un nom au moment de la compilation pour la fonction récursive est en quelque sorte un bonus. =)

Ceci est applicable dans les langues qui supportent fonctions lambda . La nature des lambdas basée sur expression signifie généralement qu'ils ne peuvent pas se nommer par eux-mêmes. Et contourner ce problème en déclarant la variable, y faisant référence, puis en lui affectant le lambda, pour compléter la boucle d'auto-référence, est fragile. La variable lambda peut être copiée et la variable d'origine réattribuée, ce qui casse l'auto-référence.

Les combinateurs-Y sont lourds à mettre en oeuvre et à utiliser souvent dans typage statique langages (que langages procéduraux souvent), car généralement les restrictions de dactylographie exigent le nombre d'arguments pour la fonction en question doit être connue au moment de la compilation. Cela signifie qu'un y-combinator doit être écrit pour tout nombre d'arguments qu'il faut utiliser.

Vous trouverez ci-dessous un exemple d'utilisation et de fonctionnement d'un Y-Combinator, en C #.

L'utilisation d'un combinateur en Y implique une manière "inhabituelle" de construire une fonction récursive. Tout d'abord, vous devez écrire votre fonction sous forme de code appelant une fonction préexistante, plutôt que lui-même:

// Factorial, if func does the same thing as this bit of code...
x == 0 ? 1: x * func(x - 1);

Ensuite, vous transformez cela en une fonction qui prend une fonction à appeler et renvoie une fonction qui le fait. Cela s'appelle une fonctionnelle, car elle prend une fonction et effectue avec elle une opération qui aboutit à une autre fonction.

// 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);

Vous avez maintenant une fonction qui prend une fonction et renvoie une autre fonction qui ressemble en quelque sorte à une factorielle, mais au lieu de s’appeler elle-même, elle appelle l’argument transmis à la fonction externe. Comment faites-vous cela la factorielle? Passez la fonction interne à elle-même. Le Y-Combinator fait cela, en étant une fonction avec un nom permanent, qui peut introduire la récursion.

// 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.
}

Plutôt que l'appel factoriel lui-même, il se produit que le factoriel appelle le générateur factoriel (renvoyé par l'appel récursif de Y-Combinator). Et en fonction de la valeur actuelle de t, la fonction renvoyée par le générateur appelle le générateur à nouveau, avec t-1, ou retourne simplement 1, mettant fin à la récursivité.

C'est compliqué et cryptique, mais tout bouge au moment de l'exécution, et la clé de son fonctionnement est une "exécution différée" et la fragmentation de la récursivité pour couvrir deux fonctions. Le F interne est transmis sous forme d'argument , à appeler à la prochaine itération, uniquement si nécessaire .

278
Chris Ammerman

J'ai soulevé cette question de http://www.mail-archive.com/[email protected]/msg02716.html qui est une explication que j'ai écrite il y a plusieurs années.

J'utiliserai JavaScript dans cet exemple, mais de nombreux autres langages fonctionneront également.

Notre objectif est de pouvoir écrire une fonction récursive de 1 variable en utilisant seulement les fonctions de 1 variable et aucune affectation, en définissant les choses par leur nom, etc. (Pourquoi est-ce notre objectif est-il une autre question, prenons cela comme le défi êtes donné.) Semble impossible, hein? Par exemple, implémentons factorial.

La première étape consiste à dire que nous pourrions le faire facilement si nous trichions un peu. En utilisant les fonctions de 2 variables et d'affectation, nous pouvons au moins éviter d'avoir à utiliser une affectation pour configurer la récursion.

// 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
);

Voyons maintenant si nous pouvons tricher moins. D'abord, nous utilisons les affectations, mais nous n'en avons pas besoin. Nous pouvons simplement écrire X et Y en ligne.

// 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
);

Mais nous utilisons des fonctions de 2 variables pour obtenir une fonction de 1 variable. Pouvons-nous résoudre ce problème? Eh bien, un type malin du nom de Haskell Curry a une astuce géniale: si vous avez de bonnes fonctions d'ordre supérieur, vous n'avez besoin que de fonctions à 1 variable. La preuve en est que vous pouvez obtenir des fonctions de 2 variables (ou plus dans le cas général) en 1 variable avec une transformation de texte purement mécanique comme celle-ci:

// Original
F = function (i, j) {
  ...
};
F(i,j);

// Transformed
F = function (i) { return function (j) {
  ...
}};
F(i)(j);

où ... reste exactement le même. (Cette astuce est appelée "currying" d'après son inventeur. Le langage Haskell est également nommé comme Haskell Curry. Fichier que sous trivia inutile.) Maintenant appliquez cette transformation partout et nous obtenons notre version finale.

// 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
);

N'hésitez pas à l'essayer. alert () qui retourne, liez-le à un bouton, peu importe. Ce code calcule les factorielles, de manière récursive, sans utiliser d'affectation, de déclarations ou de fonctions de 2 variables. (Mais essayer de retracer son fonctionnement risque de vous faire tourner la tête. Et le remettre à plat, sans la dérivation, juste légèrement reformaté donnera un code qui ne manquera pas de dérouter et de confondre.)

Vous pouvez remplacer les 4 lignes qui définissent de manière récursive factorielle par toute autre fonction récursive de votre choix.

98
btilly

Je me demande s'il est utile d'essayer de construire cela à partir de la base. Voyons voir. Voici une fonction factorielle récursive de base:

function factorial(n) {
    return n == 0 ? 1 : n * factorial(n - 1);
}

Refactorisons et créons une nouvelle fonction appelée fact qui renvoie une fonction de calcul factoriel anonyme au lieu d'effectuer le calcul lui-même:

function fact() {
    return function(n) {
        return n == 0 ? 1 : n * fact()(n - 1);
    };
}

var factorial = fact();

C'est un peu bizarre, mais il n'y a rien de mal à cela. Nous générons simplement une nouvelle fonction factorielle à chaque étape.

La récursion à ce stade est encore assez explicite. La fonction fact doit connaître son propre nom. Paramétrons l'appel récursif:

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);

C'est bien, mais recurser a toujours besoin de connaître son propre nom. Paramétrons cela aussi:

function recurser(f) {
    return fact(function(x) {
        return f(f)(x);
    });
}

var factorial = recurser(recurser);

Maintenant, au lieu d'appeler recurser(recurser) directement, créons une fonction wrapper qui renvoie le résultat obtenu:

function Y() {
    return (function(f) {
        return f(f);
    })(recurser);
}

var factorial = Y();

Nous pouvons maintenant nous débarrasser complètement du nom recurser; c'est juste un argument de la fonction interne de Y, qui peut être remplacé par la fonction elle-même:

function Y() {
    return (function(f) {
        return f(f);
    })(function(f) {
        return fact(function(x) {
            return f(f)(x);
        });
    });
}

var factorial = Y();

Le seul nom externe toujours référencé est fact, mais il devrait être clair à présent que cela est facilement paramétrable, créant ainsi la solution complète et générique:

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);
    };
});
80
Wayne Burkett

La plupart des réponses ci-dessus décrivent ce que le combinateur-Y est mais pas ce qu'il est pour .

Les combinateurs à point fixe sont utilisés pour montrer que lambda calcul est turing complete . Ceci est un résultat très important dans la théorie du calcul et fournit une base théorique pour programmation fonctionnelle .

L’étude des combinateurs à point fixe m’a également aidé à comprendre vraiment la programmation fonctionnelle. Je n'ai cependant jamais trouvé d'utilisation dans la programmation.

48
Jørgen Fogh

y-combinator in JavaScript :

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 : J'ai beaucoup appris en regardant le code, mais celui-ci est un peu difficile à avaler sans arrière-plan - désolé pour cela. Avec certaines connaissances générales présentées par d’autres réponses, vous pouvez commencer à distinguer ce qui se passe.

La fonction Y est le "Y-combinator". Regardez maintenant la ligne var factorial où Y est utilisé. Notez que vous lui passez une fonction qui a un paramètre (dans cet exemple, recurse) qui sera également utilisé plus tard dans la fonction interne. Le nom du paramètre devient fondamentalement le nom de la fonction interne lui permettant d'effectuer un appel récursif (puisqu'il utilise recurse() dans sa définition.) Le y-combinator effectue la magie d'associer la fonction interne par ailleurs anonyme au nom du paramètre de la fonction passée à Y.

Pour une explication complète de la façon dont Y fait la magie, consultez le article lié (pas par moi d'ailleurs).

23
Zach

Pour les programmeurs qui n'ont pas rencontré la programmation fonctionnelle en profondeur et ne veulent pas commencer maintenant, mais qui sont curieux:

Le Y Combinator est une formule qui vous permet d'implémenter la récursion dans une situation où les fonctions ne peuvent pas avoir de nom mais peuvent être transmises comme arguments, utilisées comme valeurs de retour et définies dans d'autres fonctions.

Cela fonctionne en passant la fonction à elle-même en tant qu'argument, afin qu'elle puisse s'appeler elle-même.

Cela fait partie du lambda calcul, qui est en fait un calcul mais est en réalité un langage de programmation et est assez fondamental pour l’informatique et en particulier pour la programmation fonctionnelle.

La valeur pratique quotidienne du Y Combinator est limitée dans la mesure où les langages de programmation ont tendance à vous permettre de nommer des fonctions.

Au cas où vous auriez besoin de l'identifier dans un alignement de police, cela ressemble à ceci:

Y = λf. (Λx.f (x x)) (λx.f (x x))

Vous pouvez généralement le repérer à cause de la répétition de (λx.f (x x)).

Les symboles λ sont la lettre grecque lambda, qui donne son nom au calcul lambda, et il existe de nombreux termes de style (λx.t) parce que le calcul lambda ressemble à cela.

17
El Zorko

D'autres réponses apportent une réponse assez concise à cela, sans un fait important: vous n'avez pas besoin d'implémenter un combinateur à points fixes de manière aussi compliquée et cela ne sert à rien. (Sauf "look, je sais ce que Y-combinator est"). C'est un concept théorique important, mais de peu de valeur pratique.

11
Ales Hakl

Voici une implémentation JavaScript de Y-Combinator et de la fonction Factorial (extrait de l'article de Douglas Crockford, disponible à l'adresse suivante: 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);
6
xgMz

Un Y-Combinator est un autre nom pour un condensateur de flux.

6
Jon Davis

Récursion anonyme

Un combinateur à virgule fixe est une fonction d'ordre supérieur fix qui, par définition, vérifie l'équivalence

forall f.  fix f  =  f (fix f)

fix f représente une solution x à l'équation à virgule fixe

               x  =  f x

La factorielle d'un nombre naturel peut être prouvée par

fact 0 = 1
fact n = n * fact (n - 1)

En utilisant fix, des preuves constructives arbitraires sur des fonctions générales/μ-récursives peuvent être dérivées sans autoréférentialité non-identique.

fact n = (fix fact') n

fact' rec n = if n == 0
                then 1
                else n * rec (n - 1)

tel que

   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

Cette preuve formelle que

fact 3  =  6

utilise méthodiquement l’équivalence du combinateur à virgule fixe pour réécrit

fix fact'  ->  fact' (fix fact')

Lambda calcul

Le formalisme lambda calcul non typé consiste en une grammaire sans contexte

E ::= v        Variable
   |  λ v. E   Abstraction
   |  E E      Application

v s'étend sur des variables, avec les règles beta et eta réduction

(λ x. B) E  ->  B[x := E]                                 Beta
  λ x. E x  ->  E          if x doesn’t occur free in E   Eta

La réduction bêta substitue toutes les occurrences libres de la variable x dans le corps de l'abstraction ("fonction") B par l'expression ("argument") E. Eta réduction élimine les abstractions redondantes. Il est parfois omis du formalisme. Une expression irréductible , à laquelle aucune règle de réduction ne s'applique, est dans normal ou forme canonique .

λ x y. E

est un raccourci pour

λ x. λ y. E

(multiarité d'abstraction),

E F G

est un raccourci pour

(E F) G

(associativité à gauche de l'application),

λ x. x

et

λ y. y

are alpha-equivalent .

L'abstraction et l'application sont les deux seules “primitives de langage” du lambda calcul, mais elles permettent encoding de données et d'opérations arbitrairement complexes.

Les chiffres de l'Église sont un codage des nombres naturels similaires aux naturels peano-axiomatiques.

   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
    . . .

Une preuve formelle que

1 + 2  =  3

en utilisant la règle de réécriture de la réduction bêta:

   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

Combinateurs

Dans le lambda calcul, combinators sont des abstractions qui ne contiennent aucune variable libre. Le plus simplement: I, le combinateur d'identité

λ x. x

isomorphe à la fonction d'identité

id x = x

Ces combinateurs sont les opérateurs primitifs de combinator calculi comme le système SKI.

S  =  λ x y z. x z (y z)
K  =  λ x y. x
I  =  λ x. x

La réduction bêta n'est pas normalisant fortement ; toutes les expressions réductibles, "redexes", ne convergent pas vers la forme normale sous réduction bêta. Un exemple simple est l’application divergente du combinateur oméga ω

λ x. x x

à lui-même:

   (λ x. x x) (λ y. y y)
=  (λ y. y y) (λ y. y y)
. . .
=  _|_                     Bottom

La réduction des sous-expressions les plus à gauche ("têtes") est priorisée. Ordre applicatif normalise les arguments avant la substitution, ordre normal ne le fait pas. Les deux stratégies sont analogues à une évaluation rapide, par ex. C et évaluation paresseuse, par exemple Haskell.

   K          (I a)        (ω ω)
=  (λ k l. k) ((λ i. i) a) ((λ x. x x) (λ y. y y))

diverge sous une réduction bêta de l'ordre applicatif avide

=  (λ 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))
. . .
=  _|_

depuis dans strict sémantique

forall f.  f _|_  =  _|_

mais converge sous la réduction bêta d'ordre normal paresseux

=  (λ l. ((λ i. i) a)) ((λ x. x x) (λ y. y y))
=  (λ l. a) ((λ x. x x) (λ y. y y))
=  a

Si une expression a une forme normale, la réduction de la bêta d'ordre normal la trouvera.

Y

La propriété essentielle du Y combinateur à virgule fixe

λ f. (λ x. f (x x)) (λ x. f (x x))

est donné par

   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))
. . .                                      . . .

L'équivalence

Y g  =  g (Y g)

est isomorphe à

fix f  =  f (fix f)

Le calcul lambda non typé peut coder des preuves constructives arbitraires sur des fonctions générales/μ-récursives.

 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

(Multiplication retardée, confluence)

Pour le calcul lambda non typé de Churchian, il a été démontré qu’il existe une infinité récursivement dénombrable de combinateurs à virgule fixe en plus de 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))
  . . .

La réduction de la bêta d'ordre normal transforme le calcul lambda non typé non étendu en un système de réécriture Turing-complete.

En Haskell, le combinateur en virgule fixe peut être implémenté avec élégance

fix :: forall t. (t -> t) -> t
fix f = f (fix f)

La paresse de Haskell se normalise avant que toutes les sous-expressions aient été évaluées.

primes :: Integral t => [t]
primes = sieve [2 ..]
   where
      sieve = fix (\ rec (p : ns) ->
                     p : rec [n | n <- ns
                                , n `rem` p /= 0])

6
user6428287

J'ai écrit une sorte de "guide idiot" au Y-Combinator dans Clojure et Scheme afin de m'aider à le maîtriser. Ils sont influencés par le matériel de "The Little Schemer"

Dans le schéma: https://Gist.github.com/z5h/238891

ou Clojure: https://Gist.github.com/z5h/5102747

Les deux tutoriels sont entrecoupés de commentaires et doivent être copiés et collés dans votre éditeur favori.

5
z5h

Le Y-combinator implémente la récursion anonyme. Donc au lieu de

function fib( n ){ if( n<=1 ) return n; else return fib(n-1)+fib(n-2) }

tu peux faire

function ( fib, n ){ if( n<=1 ) return n; else return fib(n-1)+fib(n-2) }

bien entendu, le combinateur y ne fonctionne que dans les langues appelées par noms. Si vous souhaitez utiliser cela dans n'importe quel langage appel par valeur normal, vous aurez besoin du combinateur z associé (le combinateur y divergent/est doté d'une boucle infinie).

4
Andrew

Voici les réponses aux questions originales , compilées à partir de l'article (qui est TOTALEMENT à lire) mentionnées dans le réponse de Nicholas Mancuso , ainsi comme autres réponses:

Qu'est-ce qu'un combinateur Y?

Un Y-combinator est une "fonctionnelle" (ou une fonction d'ordre supérieur - une fonction qui opère sur d'autres fonctions) qui prend un seul argument, c'est-à-dire une fonction non récursive, et renvoie une version de la fonction qui est récursif.


Un peu récursif =), mais définition plus détaillée:

Un combinateur - est simplement une expression lambda sans variables libres.
Variable libre - est une variable qui n'est pas une variable liée.
Variable liée - variable contenue dans le corps d'une expression lambda dont le nom de variable est l'un des arguments.

Une autre façon de penser est que combinator est une telle expression lambda, dans laquelle vous pouvez remplacer le nom d'un combinateur par sa définition partout où il se trouve et que tout fonctionne encore (vous entrerez dans une boucle infinie si combinator référence à lui-même, à l'intérieur du corps lambda).

Le combinateur en Y est un combinateur en virgule fixe.

Le point fixe d'une fonction est un élément du domaine de la fonction mappé sur lui-même par la fonction.
C'est-à-dire que c est un point fixe de la fonction f(x) si f(c) = c
Cela signifie f(f(...f(c)...)) = fn(c) = c

Comment fonctionnent les combinateurs?

Les exemples ci-dessous supposent fort + dynamique saisie:

Y-combinator paresseux (ordre normal):
Cette définition s’applique aux langues avec évaluation paresseuse (aussi: différée, appel par besoin) - stratégie d’évaluation qui retarde l’évaluation d’une expression jusqu’à ce que sa valeur soit nécessaire

Y = λf.(λx.f(x x)) (λx.f(x x)) = λf.(λx.(x x)) (λx.f(x x))

Cela signifie que, pour une fonction donnée f (qui est une fonction non récursive), la fonction récursive correspondante peut être obtenue d'abord en calculant λx.f(x x), puis en appliquant cette expression lambda à elle-même.

Y-combinateur strict (ordre applicatif):
Cette définition s’applique aux langues à évaluation stricte (également: désireux, gourmand): stratégie d’évaluation dans laquelle une expression est évaluée dès qu’elle est liée à une variable.

Y = λf.(λx.f(λy.((x x) y))) (λx.f(λy.((x x) y))) = λf.(λx.(x x)) (λx.f(λy.((x x) y)))

C'est pareil qu'un paresseux dans sa nature, il a juste un wrappers supplémentaire λ pour retarder l'évaluation du corps du lambda. J'ai demandé ne autre question , un peu liée à ce sujet.

À quoi servent-ils?

Volé emprunté à réponse de Chris Ammerman: Y-combinator généralise la récursivité, en résumant sa mise en oeuvre, la séparant ainsi du travail réel de la fonction en question.

Même si Y-combinator a des applications pratiques, il s’agit principalement d’un concept théorique dont la compréhension élargira votre vision globale et augmentera probablement vos compétences en matière d’analyse et de développement.

Sont-ils utiles dans les langages procéduraux?

Comme déclaré par Mike Vanier : il est possible de définir un Y Combinator dans de nombreux langages statiques, mais (au moins dans les exemples que j'ai vus ) de telles définitions nécessitent généralement un hackery de type non évident, car le Y Combinator n’a pas lui-même de type statique simple. Cela dépasse le cadre de cet article, je ne le mentionnerai donc pas davantage

Et comme mentionné par Chris Ammerman : la plupart des langages procéduraux utilisent le typage statique.

Alors répondez à celui-ci - pas vraiment.

4
Filipp W.

Cet opérateur peut vous simplifier la vie:

var Y = function(f) {
    return (function(g) {
        return g(g);
    })(function(h) {
        return function() {
            return f.apply(h(h), arguments);
        };
    });
};

Ensuite, vous évitez la fonction supplémentaire:

var fac = Y(function(n) {
    return n == 0 ? 1 : n * this(n - 1);
});

Enfin, vous appelez fac(5).

3
Tires

Un combinateur à point fixe (ou opérateur à point fixe) est une fonction d'ordre supérieur qui calcule un point fixe d'autres fonctions. Cette opération est pertinente dans la théorie des langages de programmation car elle permet la mise en œuvre de la récursion sous la forme d'une règle de réécriture, sans prise en charge explicite du moteur d'exécution du langage. (src Wikipedia)

3
Thomas Wagner

Je pense que la meilleure façon de répondre à cette question est de choisir une langue, telle que 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));
    }
}

Maintenant, réécrivez-le pour qu'il n'utilise pas le nom de la fonction à l'intérieur de la fonction, mais l'appelle quand même récursivement.

Le seul endroit où le nom de fonction factorial devrait être vu est sur le site de l'appel.

Astuce: vous ne pouvez pas utiliser les noms de fonctions, mais vous pouvez utiliser les noms de paramètres.

Travailler le problème. Ne le regarde pas. Une fois que vous aurez résolu le problème, vous comprendrez quel problème le y-combinator résout.

0
zumalifeguard