J'ai la méthode d'extension suivante:
public static IEnumerable<T> Apply<T>(
[NotNull] this IEnumerable<T> source,
[NotNull] Action<T> action)
where T : class
{
source.CheckArgumentNull("source");
action.CheckArgumentNull("action");
return source.ApplyIterator(action);
}
private static IEnumerable<T> ApplyIterator<T>(this IEnumerable<T> source, Action<T> action)
where T : class
{
foreach (var item in source)
{
action(item);
yield return item;
}
}
Il applique simplement une action à chaque élément de la séquence avant de la renvoyer.
Je me demandais si je devais appliquer l' Pure
attribut (des annotations Resharper) à cette méthode, et je peux voir des arguments pour et contre.
Avantages:
- à proprement parler, il est pur; simplement l'appeler sur une séquence ne modifie pas la séquence (elle renvoie une nouvelle séquence) ou ne fait aucun changement d'état observable
- l'appeler sans utiliser le résultat est clairement une erreur, car il n'a aucun effet à moins que la séquence ne soit énumérée, alors j'aimerais que Resharper m'avertisse si je fais cela.
Les inconvénients:
- même si la
Apply
méthode elle-même est pure, l'énumération de la séquence résultante fera des changements d'état observables (ce qui est le point de la méthode). Par exemple,items.Apply(i => i.Count++)
changera les valeurs des éléments à chaque fois qu'il est énuméré. Donc, appliquer l'attribut Pure est probablement trompeur ...
Qu'est-ce que tu penses? Dois-je appliquer l'attribut ou non?
c#
pure-function
Thomas Levesque
la source
la source
Réponses:
Non, ce n'est pas pur, car cela a un effet secondaire. Concrètement, il fait appel
action
à chaque élément. De plus, ce n'est pas threadsafe.La propriété principale des fonctions pures est qu'elles peuvent être appelées un certain nombre de fois et ne font jamais autre chose que retourner la même valeur. Ce n'est pas votre cas. De plus, être pur signifie que vous n'utilisez rien d'autre que les paramètres d'entrée. Cela signifie qu'il peut être appelé à tout moment à partir de n'importe quel thread et ne provoquer aucun comportement inattendu. Encore une fois, ce n'est pas le cas de votre fonction.
En outre, vous pourriez vous tromper sur une chose: la pureté des fonctions n'est pas une question de pour ou de contre. Un seul doute, qu'il puisse avoir un effet secondaire, suffit à le rendre non pur.
Eric Lippert soulève un bon point. Je vais utiliser http://msdn.microsoft.com/en-us/library/dd264808(v=vs.110).aspx dans le cadre de mon contre-argument. Surtout la ligne
Disons que nous créons une méthode comme celle-ci:
Tout d'abord, cela suppose que
GetEnumerator
c'est pur aussi (je ne peux pas vraiment trouver de source à ce sujet). Si c'est le cas, alors selon la règle ci-dessus, nous pouvons annoter cette méthode avec [Pure], car elle ne modifie que l'instance qui a été créée dans le corps lui-même. Après cela, nous pouvons composer ceci et leApplyIterator
, ce qui devrait se traduire par une fonction pure, non?Non. Cette composition n'est pas pure, même quand les deux
Count
etApplyIterator
sont pures. Mais je construis peut-être cet argument sur une mauvaise prémisse. Je pense que l'idée que les instances créées dans la méthode sont exemptées de la règle de pureté est soit erronée, soit du moins pas suffisamment spécifique.la source
where T : class
, mais si l'OP le mettait simplement,where T : strut
ce serait VRAI pur.sequence.Apply(action)
n'a aucun effet secondaire; si c'est le cas, indiquez l'effet secondaire qu'il a. Maintenant, appelersequence.Apply(action).GetEnumerator().MoveNext()
a un effet secondaire, mais nous le savions déjà; ça mute l'énumérateur! Pourquoi devrait-sequence.Apply(action)
on considérer impur, car appelerMoveNext
est impur, maissequence.Where(predicate)
être considéré comme pur?sequence.Where(predicate).GetEnumerator().MoveNext()
est tout aussi impur.GetEnumerator
, à part l'allocation d'un énumérateur dans son état initial?Je ne suis pas d'accord avec les réponses d' Euphoric et de Robert Harvey . C'est absolument une fonction pure; le problème est que
est très peu clair ce que signifie le premier "ça". Si "cela" signifie une de ces fonctions, alors ce n'est pas vrai; aucune de ces fonctions ne fait cela; le
MoveNext
de l'énumérateur de la séquence fait cela, et il "renvoie" l'élément via laCurrent
propriété, pas en le renvoyant.Ces séquences sont énumérées paresseusement , pas avec empressement , il n'est donc certainement pas le cas que l'action soit appliquée avant que la séquence ne soit renvoyée par
Apply
. L'action est appliquée après le retour de la séquence, si elleMoveNext
est appelée sur un énumérateur.Comme vous le constatez, ces fonctions prennent une action et une séquence et renvoient une séquence; la sortie dépend de l'entrée et aucun effet secondaire n'est produit, ce sont donc des fonctions pures.
Maintenant, si vous créez un énumérateur de la séquence résultante, puis appelez MoveNext sur cet itérateur, la méthode MoveNext n'est pas pure, car elle appelle l'action et produit un effet secondaire. Mais nous savions déjà que MoveNext n'était pas pur car il mute l'énumérateur!
Maintenant, comme pour votre question, devriez-vous appliquer l'attribut: je n'appliquerais pas l'attribut parce que je n'écrirais pas cette méthode en premier lieu . Si je veux appliquer une action à une séquence, j'écris
ce qui est bien clair.
la source
ForEach
méthode d'extension, qui ne fait intentionnellement pas partie de Linq car son objectif est de produire des effets secondaires ...Any()
fil du temps; l'action sera exécutée encore et encore, mais uniquement sur le premier élément! Une séquence doit être une séquence de valeurs ; si vous voulez une séquence d' actions, faites unIEnumerable<Action>
.action
, donc la pureté deaction
n'est pas pertinente. Je sais qu'il ressemble à ce qu'il appelleaction
, mais cette méthode est un sucre syntaxique pour deux méthodes, une qui renvoie un énumérateur, et une qui est celleMoveNext
de l'énumérateur. Le premier est clairement pur, et le second ne l'est clairement pas. Regardez-le de cette façon: diriez-vous queIEnumerable ApplyIterator(whatever) { return new MyIterator(whatever); }
c'est pur? Parce que c'est vraiment la fonction.ApplyIterator
méthode revient immédiatement . Aucun code dans le corps deApplyIterator
n'est exécuté jusqu'au premier appel àMoveNext
l'énumérateur de l'objet renvoyé. Maintenant que vous le savez, vous pouvez déduire la réponse à ce puzzle: blogs.msdn.com/b/ericlippert/archive/2007/09/05/… La réponse est ici: blogs.msdn.com/b/ericlippert/archive / 2007/09/06 /…Ce n'est pas une fonction pure, donc l'application de l'attribut Pure est trompeuse.
Les fonctions pures ne modifient pas la collection d'origine, et peu importe que vous passiez une action sans effet ou non; c'est toujours une fonction impure car son intention est de provoquer des effets secondaires.
Si vous souhaitez rendre la fonction pure, copiez la collection dans une nouvelle collection, appliquez les modifications apportées par l'action à la nouvelle collection et renvoyez la nouvelle collection, en laissant la collection d'origine inchangée.
la source
item
est un type de référence, il modifie la collection d'origine, même si vous retournezitem
dans un itérateur. Voir stackoverflow.com/questions/1538301action
pourrait avoir des effets secondaires autres que la modification de l'élément qui lui est transmis.()=>{}
est convertible en Action, et c'est une fonction pure. Ses sorties dépendent uniquement de ses entrées et il n'a pas d'effets secondaires observables.À mon avis, le fait qu'il reçoive une action (et non quelque chose comme PureAction) ne le rend pas pur.
Et je suis même en désaccord avec Eric Lippert. Il a écrit ceci "() => {} est convertible en Action, et c'est une fonction pure. Ses sorties dépendent uniquement de ses entrées et il n'a pas d'effets secondaires observables".
Eh bien, imaginez qu'au lieu d'utiliser un délégué, ApplyIterator invoquait une méthode nommée Action.
Si l'action est pure, alors le ApplyIterator est pur aussi. Si l'action n'est pas pure, alors le ApplyIterator ne peut pas être pur.
Compte tenu du type de délégué (et non de la valeur donnée réelle), nous n'avons pas la garantie qu'il sera pur, donc la méthode se comportera comme une méthode pure uniquement lorsque le délégué est pur. Donc, pour le rendre vraiment pur, il devrait recevoir un délégué pur (et cela existe, nous pouvons déclarer un délégué comme [Pure], donc nous pouvons avoir une PureAction).
En l'expliquant différemment, une méthode Pure devrait toujours donner le même résultat avec les mêmes entrées et ne devrait pas générer de changements observables. ApplyIterator peut recevoir la même source et déléguer deux fois mais, si le délégué modifie un type de référence, la prochaine exécution donnera des résultats différents. Exemple: le délégué fait quelque chose comme item.Content + = "Changed";
Ainsi, en utilisant le ApplyIterator sur une liste de "conteneurs de chaînes" (un objet avec une propriété Content de type chaîne), nous pouvons avoir ces valeurs d'origine:
Après la première exécution, la liste aura ceci:
Et cette 3ème fois:
Ainsi, nous modifions le contenu de la liste car le délégué n'est pas pur et aucune optimisation ne peut être effectuée pour éviter d'exécuter l'appel 3 fois s'il est appelé 3 fois, car chaque exécution générera un résultat différent.
la source