Répondre

5

Oui, yield return est une forme de continuation. Bien que pour beaucoup de cas utiles, Linq fournisse des opérateurs fonctionnels qui vous permettent de brancher ensemble un générateur de séquence paresseuse, donc en C# 3 il n'est pas nécessaire d'utiliser autant de yield return (sauf si vous ajoutez plus d'extensions de style Linq à brancher les espaces dans la bibliothèque, par exemple Zip, Unfold).

Dans l'exemple, nous factorisons un entier par force brute. Essentiellement le même exemple en C# peut être fait avec les opérateurs LINQ intégré:

var factors = Enumerable.Range(2, 100) 
     .Join(Enumerable.Range(2, 100), 
       n => 1, n => 1, (i, j) => new { i, j }) 
     .First(v => v.i*v.j == 481); 

Console.WriteLine("Factors are " + factors.i + ", " + factors.j); 

Voici les points de départ sont mes deux appels à Enumerable.Range, qui est intégré à Linq mais vous pourriez vous mettre en œuvre comme:

IEnumerable<int> Range(int start, int stop) 
{ 
    for (int n = start; n < stop; n++) 
     yield return n; 
} 

Il y a deux paramètres impairs, le n => 1, n => 1 paramètres à Join. Je choisis 1 comme valeur de clé pour Join à utiliser lors de la mise en correspondance des éléments, par conséquent toutes les combinaisons correspondent et ainsi je peux tester chaque combinaison de nombres à partir des plages.

Puis je retourne la paire de valeurs dans une sorte de tuple (un type anonyme) avec:

(i, j) => new { i, j }) 

Enfin, je prends la première de ces tuple pour lequel mon test est satisfaite:

.First(v => v.i*v.j == 481); 

Mise à jour

le code dans l'appel à First n'a pas besoin d'être simplement une expression de test court. Il peut être beaucoup de code impératif qui doit être « redémarré » si le test échoue:

.First(v => 
     { 
      Console.WriteLine("Aren't lambdas powerful things?"); 

      return v.i*v.j == 481; 
     ); 

Ainsi, la partie du programme qui aura éventuellement besoin d'être redémarré avec des valeurs différentes va dans ce lambda. Chaque fois que lambda veut se relancer avec des valeurs différentes, il retourne juste false - l'équivalent d'appeler amb sans arguments.

+1

Code Nice - Mais il a un effet tout à fait différent de celui amb. Amb choisit sa valeur de telle sorte que tout le code n'échoue pas, pas seulement le calcul suivant. – Dario

+1

Le "restart" doit être limité à un contexte particulier, cependant il est implémenté; Plus précisément, il serait stupide de redémarrer tout le programme, y compris tout le travail effectué avant l'introduction des variables amb! Dans ma version, cela est rendu clair en canalisant les variables amb dans une séquence, puis le contexte redémarrant est le lambda transmis à First - voir la mise à jour ci-dessus qui peut rendre cela plus clair. Le code dans le lambda peut être arbitrairement compliqué. –

+0

D'accord, bon code, mais c'est une implémentation de force brute déterministe. Il ne s'agit pas d'une implémentation simultanée non déterministe, qui constitue selon moi deux des principaux attributs de la mise en œuvre d'un opérateur AMB. En remarque, depuis ce post, Rx est sorti et il a un opérateur Amb. –

5

Ceci n'est pas une réponse à votre question, mais cela peut vous donner ce que vous voulez.

amb est utilisé pour le calcul non déterministe. Comme vous le savez peut-être, Prolog est un langage non-déterministe qui utilise la notion d'unification pour lier des valeurs à des variables (essentiellement ce que finit par faire l'amb).

Il existe une implémentation de cette fonctionnalité en C#, appelée YieldProlog. Comme vous l'avez deviné, l'opérateur de rendement est une condition importante pour cela.

http://yieldprolog.sourceforge.net/