2010-12-13 35 views
2

Quelles sont les meilleures approches pour implémenter des monades en C#? Existe-t-il des stratégies de mise en œuvre particulières ou chaque monade est-elle mise en œuvre différemment de l'autre?Quelle est la meilleure approche pour implémenter des monades en C#

+1

LINQ. (Voir par exemple http://lorgonblog.wordpress.com/2007/12/07/monadic-parser-combinators-a-noteworthy-aside/) – Brian

+0

This http://blogs.msdn.com/b/wesdyer/archive/ 2008/01/11/les-merveilles-de-monades.aspx a aussi l'air sympa, hélas, c'est seulement un exemple d'une stratégie d'implémentation –

+0

Vraiment LINQ (http://stackoverflow.com/questions/1418106/how-much-is-there-to-linq/1418176#1418176) – Dario

Répondre

6

Pour répondre à la question au lieu de simplement la commenter, Linq est probablement la méthode la plus avancée pour effectuer des transformations monadiques en C#. Une chaîne de méthodes Linq n'est rien de plus qu'un ensemble ordonné paresseusement évalué d'opérations de traitement de liste.

Linq, bien sûr, n'est pas sorcier; plus que votre cours moyen de programmation de niveau collégial, mais encore. C'est une série de méthodes d'extension qui produisent chacune un espace réservé énumérable (la monade) contenant la logique qu'elles doivent exécuter, et une référence à leur source de données (qui peut être une autre encapsulation monadique). Vous pouvez, et beaucoup le font, ajouter des extensions supplémentaires à la bibliothèque Linq de base pour remplir les trous dans la fonctionnalité ou effectuer des actions personnalisées qui répondent à des besoins spécifiques.

Vous pouvez également créer vos propres cadres de chaîne de méthode monadique, pour faire pratiquement n'importe quoi. Presque tous les frameworks ou bibliothèques décrits comme ayant une interface de codage "fluide" sont une bibliothèque basée sur des monades. Il y a des asserters de tests unitaires fluents, des configurations ORM fluentes, et même des extensions de cartographie d'UI de domaine.

L'implémentation d'une bibliothèque monadique en C# est généralement effectuée à l'aide d'une classe statique et de méthodes statiques qui utilisent un ou plusieurs types monadiques inaccessibles en dehors de leur utilisation prévue. Par exemple, voici une bibliothèque monadique de base qui effectue une addition entière et soustraction:

public static class MonadicArithmetic 
{ 
    public static Monad Take(int input) { return new Monad(input); } 

    public class Monad 
    { 
     int theValue; 

     internal Monad(int input) { theValue = input; } 

     public Monad Add(int input){ return new Monad(theValue + input); } 
     public Monad Subtract(int input){ return new Monad(theValue - result); } 
     public int Value { get { return theValue; } } 
    } 
} 

... 

//usage 
var result = MonadicArithmetic.Take(1).Add(2).Subtract(1).Value; //2 

Il est évident que cela est très basique, et toutes les opérations sont effectuées « avec impatience ». Si ces opérations étaient complexes, cela peut ne pas être optimale, donc au lieu, laissez-les effectuer « paresseusement »:

public static class MonadicArithmetic 
{ 
    public static Monad Take(int input) { return new Monad(input); } 

    public class Monad 
    { 
     //Change the "value keeper" into a Func that will return the value; 
     Func<int> theValue; 

     //the constructor now turns the input value into a lambda 
     internal Monad(int input) { theValue =()=>input; } 
     //and another constructor is added for intra-class use that takes a lambda 
     private Monad(Func<int> input) { theValue = input; } 

     //And now the methods will create new lambdas that call the existing lambdas 
     public Monad Add(int input){ return new Monad(()=>theValue() + input); } 
     public Monad Subtract(int input){ return new Monad(()=>theValue() - input); } 

     //Finally, our Value getter at the end will evaluate the lambda, unwrapping all the nested calls 
     public int Value { get { return theValue(); } } 
    } 
} 

Même utilisation, à l'exception aucune opération ne sera effectuée avant une valeur concrète est demandée par code consommation:

//Each call just adds a shell to the nested lambdas 
var operation = MonadicArithmetic.Take(1).Add(2).Subtract(1); 

... 

//HERE's the payoff; the result is not evaluated till the call to Value_get() behind the scenes of this assignment. 
var result = operation.Value; 

Cependant, il y a un problème avec ceci. Les méthodes prennent simplement les valeurs d'entrée, et les référencent dans un lambda. Le problème est que la portée des valeurs dépend de la méthode contenant (ce qui signifie qu'elles ne vivent pas assez longtemps pour que le lambda soit évalué). Quand le getter Value() est appelé, le lambda sera évalué, et toutes ces variables hors-champ seront référencées. Au lieu de cela, nous devrions persister les valeurs dans quelque chose qui vivra au moins aussi longtemps que les lambdas. La monade est l'option évidente. Voici une solution probable:

public static class MonadicArithmetic 
{ 
    public static Monad Take(int input) { return new Monad(input); } 

    public class Monad 
    { 
     //Our value keeper is now a pure function that requires no external closures 
     Func<Func<int>, int, int> operation; 
     //and we add two new private fields; 
     //a hook to a lambda that will give us the result of all previous operations, 
     Func<int> source; 
     //... and the value for the current operation. 
     private int addend; 

     //our constructor now takes the value, stores it, and creates a simple lambda 
     internal Monad(int input) { addend = input; operation =()=>addend; } 
     //and our private constructor now builds a new Monad from scratch 
     private Monad(Func<int> prevOp, Func<Func<int>, int, int> currOp, int input) 
     { 
      source = prevOp, 
      operation = currOp, 
      addend = input; 
     } 

     //The methods will create new Monads that take the current Monad's value getter, 
     //keeping the current Monad in memory. 
     public Monad Add(int input) 
     { 
     return new Monad(this.Result, (f,i)=>f()+i, input); 
     } 

     public Monad Subtract(int input) 
     { 
     return new Monad(this.Result, (f,i)=>f()-i, input); 
     } 

     //And we change our property to a method, so it can also 
     //be used internally as a delegate 
     public int Result() { return operation(source, addend); } 
    } 
} 

//usage 
var operations = MonadicArithmetic.Take(1).Add(3).Subtract(2); 
//There are now 3 Monads in memory, each holding a hook to the previous Monad, 
//the current addend, and a function to produce the result... 

... 

//so that here, all the necessary pieces are still available. 
var result = operations.Result(); 

Ceci est le modèle de base pour une bibliothèque monadique. La méthode statique qui démarre le tout peut être une méthode d'extension, qui est le style que Linq utilise. La racine de la chaîne de procédé est la première valeur:

//using an "identity function" to convert to a monad 
var operations = 1.AsMonad().Add(2).Subtract(3); 

//performing the conversion implicitly from an overload of Add() 
var operations = 1.Add(2).Subtract(3); 

Linq pour des objets est particulièrement élégant en ce que les bibliothèques sont les méthodes d'extension qui ont un IEnumerable et retourner un IEnumerable, de sorte que le processus de conversion est traitée sans aucune surcharge ou appels de méthode explicites. Cependant, les objets IQueryable, qui masquent les arbres d'expression traduisibles, sont une nouvelle idée dans .NET 3.5, et vous devez convertir explicitement une collection en IQueryable, via la méthode AsQueryable().

+0

À la vôtre pour une réponse réelle :) Cependant, je ne parviens toujours pas à voir un certain code C# ou des détails sur une stratégie dans votre réponse –

+3

Je ne pense pas que ce que vous décrivez répond à la définition de Monad. Il semble juste être une interface fluide. Les monades ont besoin de mettre en œuvre des opérations particulières - identité, liaison et retour. Linq est monadique car il implémente ces opérations - l'opération de liaison est, par exemple, la méthode d'extension 'SelectMany'. – Enigmativity

+0

Je ne suis pas d'accord. "Dans la programmation fonctionnelle, une monade est une sorte de constructeur de type de données abstrait utilisé pour représenter des calculs (au lieu de données dans le modèle de domaine) .Monads permettent au programmeur d'enchaîner les actions pour construire un pipeline, dans lequel chaque action est décoré avec des règles de traitement supplémentaires fournies par la monade. ". Si cela est vrai, alors pratiquement toute interface fluide avec une "structure grammaticale" (nécessitant l'utilisation de types internes pour contrôler des méthodes valides) peut être étiquetée comme monadique. Dans ce cas je convertis en une monade (Take), transforme (Add/Subtract) et retourne. – KeithS