2009-03-28 8 views
4

J'apprends un peu sur la programmation des fonctions, et je me demande:Est-ce que cette méthode d'extension C# est impure et si oui, un mauvais code?

1) Si ma méthode d'extension ForEach est pure? La façon dont je l'appelle semble violer le "ne pas jouer avec l'objet qui se passe", non?

public static void ForEach<T>(this IEnumerable<T> source, Action<T> action) 
{ 
    foreach (var item in source) 
    action(item); 
} 


static void Main(string[] args) 
{ 
    List<Cat> cats = new List<Cat>() 
    { 
     new Cat{ Purring=true,Name="Marcus",Age=10}, 
     new Cat{ Purring=false, Name="Fuzzbucket",Age=25 }, 
     new Cat{ Purring=false, Name="Beanhead",Age=9 }, 
     new Cat{Purring=true,Name="Doofus",Age=3} 
    }; 


    cats.Where(x=>x.Purring==true).ForEach(x => 
    { 
     Console.WriteLine("{0} is a purring cat... purr!", x.Name); 
    }); 

    // ************************************************* 
    // Does this code make the extension method impure? 
    // ************************************************* 
    cats.Where(x => x.Purring == false).ForEach(x => 
    { 
     x.Purring = true; // purr,baby 
    }); 

    // all the cats now purr 
    cats.Where(x=>x.Purring==true).ForEach(x => 
    { 
     Console.WriteLine("{0} is a purring cat... purr!", x.Name); 
    }); 
} 

public class Cat { 
     public bool Purring; 
     public string Name; 
     public int Age; 
} 

2) Si c'est impur, est-ce mauvais code? Personnellement, je pense que cela rend le code plus propre que le vieux foreach (var item in items) { blah; }, mais je crains que, comme il pourrait être impur, il pourrait faire un gâchis.

3) Serait-ce un mauvais code s'il renvoyait IEnumerable<T> au lieu de void? Je dirais que tant que c'est impur, oui ce serait un très mauvais code car cela encouragerait à enchaîner quelque chose qui modifierait la chaîne. Par exemple, est-ce mauvais code?

// possibly bad extension 
public static IEnumerable<T> ForEach<T>(this IEnumerable<T> source, Action<T> action) 
{ 
    foreach (var item in source) 
     action(item); 

    return source; 

} 

Répondre

11

L'impureté ne signifie pas nécessairement un mauvais code. Beaucoup de gens trouvent facile et utile d'utiliser des effets secondaires pour résoudre un problème. La clé est d'abord de savoir comment le faire d'une manière pure, ainsi vous saurez quand l'impureté est appropriée :).

.NET n'a pas le concept de pureté dans le système de types, donc une méthode «pure» qui accepte des délégués arbitraires peut toujours être impure, selon la façon dont elle est appelée. Par exemple, "Où", alias "filtre", serait généralement considéré comme une fonction pure, puisqu'il ne modifie pas ses arguments ou ne modifie pas l'état global. Mais, rien ne vous empêche de mettre un tel code dans l'argument de Où. Par exemple:

things.Where(x => { Console.WriteLine("um?"); 
        return true; }) 
     .Count(); 

Donc c'est définitivement une utilisation impure de Où. Les énumérables peuvent faire ce qu'ils veulent en itérant.

Votre code est-il mauvais? Non. Utiliser une boucle foreach est tout aussi "impur" - vous modifiez toujours les objets source. J'écris du code comme ça tout le temps. Enchaînez ensemble des sélections, des filtres, etc., puis exécutez un ForEach dessus pour invoquer du travail. Tu as raison, c'est plus propre et plus facile.

Exemple: ObservableCollection. Il n'a aucune méthode AddRange pour une raison quelconque. Donc, si je veux ajouter un tas de choses, que dois-je faire?

foreach(var x in things.Where(y => y.Foo > 0)) { collection.Add(x)); } 

ou

things.Where(x => x.Foo > 0).ForEach(collection.Add); 

Je préfère le second. Au minimum, je ne vois pas comment cela peut être interprété comme étant pire que la première.

Quand est-ce mauvais code? Quand il fait du code à côté d'un endroit qui n'est pas prévu. C'est le cas de mon premier exemple utilisant Where. Et même alors, il y a des moments où la portée est très limitée et l'utilisation est claire.

Chaînage ForEach

J'ai écrit du code qui fait des choses comme ça. Pour éviter toute confusion, je lui donnerais un autre nom. La principale confusion est "est-ce immédiatement évalué ou paresseux?". ForEach implique qu'il va exécuter une boucle tout de suite. Mais quelque chose renvoyant un IEnumerable implique que les éléments seront traités au besoin. Je propose donc de donner un autre nom (« Process », « ModifySeq », « OnEach » ... quelque chose comme ça), et le rendre paresseux:

public static IEnumerable<T> OnEach(this IEnumerable<T> src, Action<T> f) { 
    foreach(var x in src) { 
    f(x); 
    yield return x; 
    } 
} 
+0

Excellente réponse. –

6

Ce n'est pas pur, car il peut appeler des méthodes impures. Je pense que par des définitions typiques, la pureté est une fermeture transitive - une fonction n'est pure que si toutes les fonctions qu'elle appelle (directement ou indirectement) sont également pures, ou si les effets de ces fonctions sont encapsulés (par exemple, ils ne muent qu'un échappement). variable locale).

+0

Fondamentalement, toute méthode publique .NET qui appelle une l'argument de la fonction est impur, car il pourrait appeler un code impur? – MichaelGG

+0

On peut imaginer un système de type où vous pouvez être 'générique' par rapport à 'pureté', et donc G (Func f) pourrait avoir G pur-quand-f-est-pur et impur-quand-f-est -impur. – Brian

+0

Mais jusqu'à ce que nous l'ayons dans le système de type, n'est-il pas utile de pouvoir distinguer HoF qui sont purs si leurs params ne sont pas contre ceux qui ne le sont pas? – MichaelGG

3

En effet, parce que votre expression lambda contient une affectation, la fonction est maintenant par définition impure. Que l'affectation soit liée à l'un des arguments ou à un autre objet défini en dehors de la fonction en cours est sans importance ... Une fonction ne doit avoir aucun effet secondaire pour être appelée pure. Voir Wikipedia pour une définition plus précise (bien que très simple), qui détaille les deux conditions qu'une fonction doit satisfaire pour être considérée comme pur (sans effets secondaires est l'un d'entre eux). Je crois que les expressions lambda sont typiquement destinées à être utilisées comme des fonctions pures (du moins j'imagine qu'elles ont été étudiées en tant que telles du point de vue mathématique), bien que C# ne soit pas rigoureux là où sont les langages purement fonctionnels. Donc, ce n'est probablement pas une mauvaise pratique, même s'il vaut vraiment la peine d'être absent qu'une telle fonction est impure.

4

Oui, ce n'est pas pur, mais c'est un peu discutable car ce n'est même pas une fonction. Comme la méthode ne renvoie rien, la seule option pour faire quoi que ce soit est d'affecter les objets que vous envoyez, ou d'affecter quelque chose de non apparenté (comme écrire dans la fenêtre de la console).

Éditer:
Pour répondre à votre troisième question; oui, c'est un mauvais code, car il semble faire quelque chose qui ne fonctionne pas. La méthode retourne une collection donc elle semble être pure, mais comme elle retourne juste la collection qui a été envoyée, elle n'est pas plus pure que la première version. Pour tout sens, la méthode doit prendre un délégué Func<T,T> à utiliser comme conversion, et retourner une collection des objets convertis:

public static IEnumerable<T> ForEach<T>(this IEnumerable<T> source, Func<T,T> converter) { 
    foreach (T item in source) { 
     yield return converter(item); 
    } 
} 

Il est bien sûr toujours à la fonction de conversion si l'appel d'extension est pur. S'il ne fait pas une copie de l'élément d'entrée, mais le modifie et le renvoie, l'appel n'est toujours pas pur.

+0

Pas une fonction dans le sens mathématique, je suis sûr que vous voulez dire ... – Noldorin

+0

@Noldorin: Non, ce n'est pas ce que je veux dire. Il n'a pas de valeur de retour, donc ce n'est pas une fonction. – Guffa

Questions connexes