Désolé pour ce titre vague - si je pouvais trouver un titre concis, je n'aurais pas à poser la question.
Supposons que j'ai un type de liste immuable. Il possède une opération Foo(x)
qui renvoie une nouvelle liste immuable avec l'argument spécifié comme élément supplémentaire à la fin. Ainsi, pour construire une liste de chaînes de caractères avec les valeurs "Hello", "immutable", "world", vous pourriez écrire :
var empty = new ImmutableList<string>();
var list1 = empty.Foo("Hello");
var list2 = list1.Foo("immutable");
var list3 = list2.Foo("word");
(Il s'agit de code C#, et je suis plus intéressé par une suggestion en C# si vous pensez que le langage est important. Ce n'est pas fondamentalement une question de langage, mais les idiomes du langage peuvent être importants).
L'important est que les listes existantes soient pas modifié par Foo
- donc empty.Count
retournerait toujours 0.
Une autre façon (plus idiomatique) d'arriver au résultat final serait :
var list = new ImmutableList<string>().Foo("Hello")
.Foo("immutable")
.Foo("word");
Ma question est la suivante : Quel est le meilleur nom pour Foo ?
EDIT 3 : Comme je le révèle plus tard, le nom du type pourrait ne pas être en réalité. ImmutableList<T>
ce qui rend la position claire. Imaginez plutôt que c'est TestSuite
et qu'il est immuable parce que l'ensemble du cadre dont il fait partie est immuable...
(Fin de l'édition 3)
Les options que j'ai trouvées jusqu'à présent :
-
Add
: commun en .NET, mais implique une mutation de la liste originale. -
Cons
: Je crois que c'est le nom normal dans les langages fonctionnels, mais sans signification pour ceux qui n'ont pas d'expérience dans ces langages. -
Plus
: mon préféré jusqu'à présent, il n'implique pas de mutation. pour moi . Apparemment, c'est aussi utilisé en Haskell mais avec des attentes légèrement différentes (un programmeur Haskell pourrait s'attendre à ce qu'il ajoute deux listes ensemble plutôt que d'ajouter une seule valeur à l'autre liste). -
With
Il est cohérent avec d'autres conventions immuables, mais n'a pas tout à fait la même "additionnalité", selon moi. -
And
: pas très descriptif. - Surcharge de l'opérateur + : je n'aime pas vraiment cela ; je pense généralement que les opérateurs ne devraient être appliqués qu'aux types de niveau inférieur. Mais je suis prêt à me laisser convaincre !
Les critères que j'utilise pour choisir sont les suivants :
- Donne l'impression correcte du résultat de l'appel de la méthode (c'est-à-dire qu'il s'agit de la liste originale avec un élément supplémentaire).
- Indique aussi clairement que possible qu'il ne modifie pas la liste existante.
- Cela semble raisonnable quand on les enchaîne comme dans le deuxième exemple ci-dessus.
Veuillez demander plus de détails si je ne suis pas assez clair...
EDIT 1 : Voici mon raisonnement pour préférer Plus
à Add
. Considérons ces deux lignes de code :
list.Add(foo);
list.Plus(foo);
A mon avis (et ceci est c'est une question personnelle), la seconde est clairement boguée - c'est comme si on écrivait "x + 5 ;" comme une déclaration à part entière. La première ligne semble correcte, jusqu'à ce que l'on se rappelle qu'elle est immuable. En fait, la manière dont l'opérateur plus ne modifie pas ses opérandes est une autre raison pour laquelle Plus
est mon préféré. Sans les inconvénients de la surcharge des opérateurs, il donne toujours les mêmes connotations, qui incluent (pour moi) la non-mutation des opérandes (ou de la cible de la méthode dans ce cas).
EDIT 2 : Raisons pour lesquelles je n'aime pas Add.
Diverses réponses sont effectivement données : "Allez-y avec Add. C'est ce que DateTime
fait, et String
a Replace
méthodes etc. qui ne rendent pas l'immuabilité évidente". Je suis d'accord - il y a un précédent ici. Cependant, j'ai vu beaucoup de personnes appeler DateTime.Add
o String.Replace
y s'attendre à une mutation . Il y a des tas de questions de newsgroups (et probablement des questions de SO si je creuse un peu) dont la réponse est "Vous ignorez la valeur de retour de String.Replace
; les chaînes sont immuables, une nouvelle chaîne est renvoyée."
Maintenant, je dois révéler une subtilité à la question - le type pourrait pas est en fait une liste immuable, mais un type immuable différent. En particulier, je travaille sur un cadre d'évaluation comparative où vous ajoutez des tests à une suite, et cela crée une nouvelle suite. Cela peut sembler évident :
var list = new ImmutableList<string>();
list.Add("foo");
ne va pas accomplir quoi que ce soit, mais cela devient une lot plus sombre lorsque vous le changez en :
var suite = new TestSuite<string, int>();
suite.Add(x => x.Length);
On dirait que ça devrait aller. Alors que ceci, pour moi, rend l'erreur plus claire :
var suite = new TestSuite<string, int>();
suite.Plus(x => x.Length);
Ça ne demande qu'à l'être :
var suite = new TestSuite<string, int>().Plus(x => x.Length);
Idéalement, j'aimerais que mes utilisateurs n'aient pas à être informés que la suite de tests est immuable. Je veux qu'ils tombent dans la fosse du succès. Ce mai ne soit pas possible, mais j'aimerais essayer.
Je m'excuse d'avoir trop simplifié la question initiale en ne parlant que d'un type de liste immuable. Toutes les collections ne sont pas aussi autodescriptives que le type ImmutableList<T>
:)
0 votes
Je pense qu'il n'y aurait aucune ambiguïté à "Ajouter" en supposant que la classe ne supporte pas à la fois les opérations mutantes et non-mutantes. Si c'était le cas, je pourrais comprendre qu'il faille être plus spécifique.
0 votes
Le problème n'est pas le nom, c'est que C# ignore automatiquement la valeur de retour. Ainsi, trouver des noms créatifs ne résoudra pas le problème sous-jacent, à savoir que la plupart des types en C# sont mutables. Sans parler de l'obtention de bons noms comme Plus pour d'autres cas (Insert, Remove, Push, Pop, etc.).
0 votes
Aussi, vous n'abusez pas assez de l'inférence de type. De BclExtras. var list = ImmutableList.CreateFromArguments("foo") ;
0 votes
Au début de Edit 1, voulez-vous dire "le premier est clairement bogué", c'est-à-dire Add plutôt que Plus ?
2 votes
@Adam : Non, le second est clairement bogué. Ils sont tous les deux en fait bogues (puisqu'ils ne font rien avec le résultat) - mais le premier n'est pas regardez buggy pour moi.
0 votes
@litb : Idée intéressante. Souffre du problème de l'abréviation, mais intéressante, certainement...
1 votes
J'ai supprimé ce que j'avais suggéré :) en fait, c'était "Concat". mais il n'y a pas d'équivalent "Remove" à cela :/
1 votes
Jason, "Uncat" sonne comme un problème d'animal de compagnie ;)
36 votes
Un uncat est-il un chien ?
7 votes
Concat/Condog... ça marche pour moi ! ;)
11 votes
Uncat - ce serait un chat zombie.
0 votes
Je suggère de choisir les 5 options les plus importantes (ou moins) et de créer des messages individuels pour que les gens puissent voter pour ou contre.
0 votes
Je supprimerais la partie où il n'est pas évident que la liste est réellement immuable. Qu'est-il arrivé aux personnes qui lisent la documentation pour apprendre ce que fait réellement une classe ?
0 votes
@mP : Dans ce cas, j'ai une forte motivation pour rendre la barrière à l'entrée aussi basse qu'humainement possible. Voir msmvps.com/blogs/jon_skeet/archive/2009/02/02/
0 votes
Je préfère nettement Plus à Add.
0 votes
Public statique T Add( T a, T b )
2 votes
@Trap : C'est nul pour ce qui est de rendre l'API fluide.
0 votes
Réponse ajoutée - juste au cas où vous ne l'auriez pas vue - j'ai pensé utiliser les commentaires pour mettre cela en évidence pour vous.
0 votes
@littlegeek : Je l'ai vu. C'est bien, mais je pense que je préfère "Plus" pour le moment.
0 votes
"cons" n'est familier qu'aux personnes qui utilisent Lisp, et pas à toutes les personnes qui font de la programmation fonctionnelle.
0 votes
Êtes-vous arrivé à une conclusion ?
0 votes
@Erik : Pas vraiment. Je pense que Plus est mon préféré pour le moment. Malheureusement, je n'ai pas le temps de travailler sur le projet en ce moment :(
0 votes
"Avec : ... n'a pas tout à fait la même 'additionnalité'" -- N'est-ce pas une bonne chose ? Vous êtes spécifiquement pas En ajoutant un élément à une collection existante, vous demandez à quoi ressemblerait la collection résultante.
0 votes
@finnw : J'ai mal exprimé mon point de vue - "avec" donne l'impression (pour moi) que vous êtes remplacement de une valeur plutôt que en ajoutant un (dans le nouvel objet). "WithExtraTest" ou quelque chose de similaire pourrait fonctionner, mais c'est un peu verbeux.
0 votes
Au risque d'être un peu verbeux, peut-être quelque chose comme : RebuildWith(), RecreateWith() ou RemakeWith().
0 votes
Je suis certainement en retard pour la fête ici... mais que diriez-vous de .Include() ?
0 votes
Lorsque vous parlez de ne pas vouloir utiliser la surcharge de l'opérateur, vous mentionnez qu'elle devrait être réservée aux types de niveau inférieur. Pourquoi cela est-il vrai ? Je pense que vous préférez .Plus parce que tout le monde sait que A+B ne modifie rien.
0 votes
C'est plus une intuition qu'autre chose, mais ce serait juste se sentir mal en quelque sorte...
0 votes
D'un autre côté, j'ai abandonné les Listes pour les Enumérables il y a longtemps, donc maintenant je suis surpris quand TOUT provoque une mutation. Vive le C# qui devient fonctionnel !
0 votes
Hors sujet, mais avez-vous rédigé des articles de blog sur les avantages et les inconvénients des listes immuables ? J'ai lu récemment la série d'articles d'Eric Lippert sur l'immuabilité, mais je n'ai pas vu ce sujet particulier abordé.
0 votes
@JustinMorgan : Non, je ne l'ai pas fait, j'en ai peur.
0 votes
Juste par curiosité, quel nom de méthode avez-vous choisi ? IMO Add est également un mauvais choix puisque si
ImmutableClass : IEnumerable
new ImmutableClass { someElement }
semble raisonnable, mais il est encore plus difficile de se rendre compte que l'initialisateur de collection ne fait rien.0 votes
@rich.okelly : Je ne me souviens pas - je n'ai pas regardé ce projet depuis un bon moment :). (Il faudra que je m'y remette un jour...)
0 votes
plus
yminus
Je m'attendrais à prendre le même type deux fois. Après tout, vous faites"abc"+"d"
pas"abc"+'d'
.0 votes
@ThomasAhle : Je ne suis pas sûr de ce que vous voulez dire - dans de nombreuses langues, "abc" + "d" est tout à fait valide ; de même, "abc" + "d" est tout à fait valide.
DateTime.Now + TimeSpan.FromMinutes(10)
est valable en C#...0 votes
@JonSkeet : N'étant pas un spécialiste du C#, cela a du sens du point de vue de l'horodatage. L'utilisation de plus avec différents types, vous fait vous demander lequel des types est utilisé pour le résultat. Au moins un plus devrait être associatif et avoir une fermeture, imho.
0 votes
@ThomasAhle : Mais
TimeSpan
yDateTime
sont déjà des types différents... Je l'utilise aussi dans Noda Time pour les décalages d'heure d'été ; c'est vraiment pratique : Instant + Offset = LocalInstant ; LocalInstant - Offset = Instant. Cela signifie que vous ne pouvez pas l'utiliser dans le mauvais sens, ce qui est charmant...0 votes
Ce problème est déjà résolu (en quelque sorte) par Enumerable.Concat<TSource>. Créez votre propre extension Concat<T>(this IEnumerable<T>, T second)
0 votes
@csauve : Concat concatène deux séquences et non la concaténation d'un seul élément dans une séquence. De plus, même "Concat" est loin d'être clair. C'est une abréviation, et une abréviation d'un mot qui est très rarement utilisé en anglais.
0 votes
Je vois ce que vous voulez dire - intuitivement, je considère que .Concat(t) n'est qu'une abréviation de .Concat(new[] {t}), qui, comme vous le dites, est une abréviation de .Concatenate(new[] {t}). Nous l'utilisons toujours dans notre base de code, ainsi qu'une surcharge similaire pour .Union(T) (pour renforcer l'unicité). Alternativement, si cela ne vous dérange pas d'écrire .Concat(new[] {t}), vous pouvez en fait simplement utiliser cette méthode d'extension et vous n'avez pas besoin de modifier votre classe du tout.
2 votes
Clojure utilise 'conj' pour conjointes. C'est un peu comme 'cons' mais le comportement est polymorphe à la collection.
0 votes
Une autre raison d'éviter
Add
est qu'il vous donne la syntaxe de l'initialisateur de collection.new ImmutableCollection<T> { a, b, c }
est inutile et ne sert à rien.0 votes
@nawfal : Oui - c'est vraiment dommage que la syntaxe des initialisateurs de collection ne supporte pas les collections immuables (via l'utilisation de la valeur de retour).
0 votes
@JonSkeet Oui, je suis d'accord avec vous sur ce point. Le plus proche que vous obtenez est avec
params
dans le constructeur pour les valeurs uniques, comme par exemplenew ImmutableCollection<T>(a, b, c)
...0 votes
@JonSkeet est votre
ImmutableList<T>
source disponible ? Fait-elle partie d'une bibliothèque ? J'aimerais bien le voir :)0 votes
@nawfal : Je n'ai pas réellement une telle chose - mais bien sûr les collections immuables de Microsoft sont maintenant disponibles de toute façon :)
0 votes
@JonSkeet oh oui j'ai raté ça !
0 votes
@rezomegreldize : On dirait que cela va modifier la collection originale cependant :(.
0 votes
Puisque personne n'a mentionné Python : en Python, les tuples sont immuables, et ils se réfèrent à cette opération comme "concaténer", par exemple avec
a=(1,2)
vous pouvez ajouter un élément aveca=a+(3,)
. L'inverse (retirer un élément d'une liste) est appelé "découpage", par exemplea=a[:-1]
. Même la concaténation de listes renvoie une nouvelle liste. Il s'agit donc d'une fonctionnalité du langage attendue par les programmeurs Python expérimentés. Il existe également tuples nommés mais ceux-ci n'ont pas de convention de dénomination des méthodes.