Jaka jest najlepsza nazwa niemutującej metody „dodawania” w niezmiennej kolekcji?


229

Przepraszam za gofrowny tytuł - gdybym mógł wymyślić zwięzły tytuł, nie musiałbym zadawać pytania.

Załóżmy, że mam niezmienny typ listy. Ma operację, Foo(x)która zwraca nową niezmienną listę z określonym argumentem jako dodatkowy element na końcu. Aby stworzyć listę ciągów znaków z wartościami „Cześć”, „niezmienny”, „świat” możesz napisać:

var empty = new ImmutableList<string>();
var list1 = empty.Foo("Hello");
var list2 = list1.Foo("immutable");
var list3 = list2.Foo("word");

(To jest kod C # i najbardziej interesuje mnie sugestia C #, jeśli uważasz, że język jest ważny. Nie jest to zasadniczo pytanie językowe, ale idiomy języka mogą być ważne.)

Ważne jest to, że istniejące listy nie są zmieniane przez Foo- więc empty.Countnadal zwracałby 0.

Innym (bardziej idiomatycznym) sposobem dojścia do wyniku końcowego byłoby:

var list = new ImmutableList<string>().Foo("Hello")
                                      .Foo("immutable")
                                      .Foo("word");

Moje pytanie brzmi: jakie jest najlepsze imię dla Foo?

EDYCJA 3 : Jak ujawnię później, nazwa typu może nie być tak naprawdę ImmutableList<T>, co wyjaśnia pozycję. Wyobraź sobie, że jest TestSuitei jest niezmienny, ponieważ cały szkielet, którego jest częścią, jest niezmienny ...

(Koniec edycji 3)

Opcje, które do tej pory wymyśliłem:

  • Add: wspólne w .NET, ale oznacza mutację oryginalnej listy
  • Cons: Uważam, że jest to normalna nazwa w językach funkcjonalnych, ale bez znaczenia dla osób bez doświadczenia w takich językach
  • Plus: jak dotąd mój ulubiony, nie oznacza to dla mnie mutacji . Najwyraźniej jest to również używane w Haskell, ale z nieco innymi oczekiwaniami (programista Haskell może oczekiwać, że doda dwie listy razem zamiast dodawać jedną wartość do drugiej listy).
  • With: zgodny z niektórymi niezmiennymi konwencjami, ale nie ma w tym samym stopniu „dodatkowości” IMO.
  • And: niezbyt opisowy.
  • Przeciążenie operatora dla +: Naprawdę tak bardzo nie lubię; Ogólnie myślę, że operatory powinny być stosowane tylko do typów niższych poziomów. Chcę się jednak przekonać!

Kryteria, których używam do wyboru to:

  • Daje prawidłowe wrażenie wyniku wywołania metody (tzn. Że jest to oryginalna lista z dodatkowym elementem)
  • Wyjaśnia, jak to możliwe, że nie mutuje istniejącej listy
  • Brzmi rozsądnie, gdy są połączone w łańcuch, jak w drugim przykładzie powyżej

Poproś o więcej szczegółów, jeśli nie wyrażę się wystarczająco jasno ...

EDIT 1: Oto moje rozumowanie dla preferując Plusdo Add. Rozważ te dwa wiersze kodu:

list.Add(foo);
list.Plus(foo);

Moim zdaniem (i jest to sprawa osobista) ta ostatnia jest wyraźnie błędna - przypomina pisanie „x + 5;” jako samodzielne oświadczenie. Pierwszy wiersz wygląda, jakby był w porządku, dopóki nie przypomnisz sobie, że jest niezmienny. W rzeczywistości sposób, w jaki operator plus sam nie mutuje operandów, jest kolejnym powodem, dla którego Plusjestem moim ulubionym. Bez lekkiej uciążliwości przeciążania operatora wciąż daje to te same konotacje, które obejmują (dla mnie) nie mutowanie operandów (w tym przypadku cel metody).

EDYCJA 2: Powody, dla których nie lubisz Dodaj.

Różne odpowiedzi są skuteczne: „Idź z Add. To właśnie DateTimedziała i Stringma Replacemetody itp., Które nie sprawiają, że niezmienność jest oczywista”. Zgadzam się - tutaj jest pierwszeństwo. Jednak widziałem mnóstwo ludzi zadzwonić DateTime.Addalbo String.Replacei oczekiwać mutację . Istnieje mnóstwo pytań do grup dyskusyjnych (i prawdopodobnie TAK, jeśli się rozejrzę), na które odpowiada: „Ignorujesz zwracaną wartość String.Replace; ciągi są niezmienne, zwracany jest nowy ciąg”.

Teraz powinienem ujawnić subtelność pytania - ten typ może nie być listą niezmienną, ale innym niezmiennym typem. W szczególności pracuję nad strukturą testów porównawczych, w której dodajesz testy do pakietu, a to tworzy nowy pakiet. Może być oczywiste, że:

var list = new ImmutableList<string>();
list.Add("foo");

niczego nie osiągnie, ale staje się o wiele bardziej mętny, kiedy zmienisz na:

var suite = new TestSuite<string, int>();
suite.Add(x => x.Length);

Wygląda na to, że powinno być w porządku. Tymczasem dla mnie to wyjaśnia błąd:

var suite = new TestSuite<string, int>();
suite.Plus(x => x.Length);

To po prostu błaga:

var suite = new TestSuite<string, int>().Plus(x => x.Length);

Idealnie chciałbym, aby moi użytkownicy nie musieli być informowani, że zestaw testów jest niezmienny. Chcę, żeby wpadły w pułapkę sukcesu. To może nie być możliwe, ale chciałbym spróbować.

Przepraszam za nadmierne uproszczenie pierwotnego pytania, mówiąc tylko o niezmiennym typie listy. Nie wszystkie kolekcje są tak samoopisowe jak ImmutableList<T>:)


2
@Adam: Nie, ten drugi jest wyraźnie wadliwy. Oboje są w rzeczywistości błędne (ponieważ nie robią nic z rezultatem) - ale pierwszy nie wydaje mi się błędny.
Jon Skeet

36
Czy owczarek jest psem?
Michael Myers

7
Concat / Condog ... działa dla mnie! ;)
gnovice

11
Uncat - to byłby kot zombie.
Erik Forbes,

2
@ Trap: To jest do bani, jeśli chodzi o uczynienie API płynnym.
Jon Skeet

Odpowiedzi:


130

W takich sytuacjach zwykle idę z tym Concat. Zwykle oznacza to, że tworzony jest nowy obiekt.

var p = listA.Concat(listB);
var k = listA.Concat(item);

3
To jest to, czego używam, ponieważ tego właśnie używa System.Linq.Enumerable - decyzja jest już podjęta. :) Nie mogę być jedyną osobą, która zdefiniowała własne przeciążenie rozszerzenia Concat w IEnumerable, aby zaakceptować pojedynczą wartość do dołączenia.
Daniel Earwicker

9
+1 To poprawna odpowiedź dla utrzymania spójności nazewnictwa w ramach.
Sam Harwell,

O ile go nie brakuje, System.Linq.Enumerable (i ogólnie Linq) używa Concat () tylko do „sekwencji + sekwencji”, nigdy „pozycji + sekwencji”. Problem, że ktoś „mógłby oczekiwać, że doda dwie listy razem zamiast dodawać jedną wartość do drugiej listy” został wyraźnie podany jako powód, aby nie używać jednej z pozostałych oryginalnych opcji.
Ssswift 11.01.18

119

Wybrałbym minusy, z jednego prostego powodu: oznacza to dokładnie to, czego chcesz.

  1. Jestem wielkim fanem mówienia dokładnie o co mi chodzi, szczególnie w kodzie źródłowym. Nowicjusz będzie musiał spojrzeć na definicję Wady tylko raz, ale potem przeczyta ją i użyje tysiąc razy. Uważam, że w dłuższej perspektywie przyjemniej jest pracować z systemami, które ułatwiają wspólną sprawę, nawet jeśli koszt początkowy jest nieco wyższy.

  2. Fakt, że byłoby to „bez znaczenia” dla osób bez doświadczenia w FP, jest w rzeczywistości dużą zaletą. Jak zauważyłeś, wszystkie pozostałe słowa, które znalazłeś, mają już jakieś znaczenie, a to znaczenie jest albo nieco inne, albo niejednoznaczne. Nowa koncepcja powinna mieć nowe słowo (lub w tym przypadku starą). Wolałbym, żeby ktoś musiał sprawdzić definicję Wady, niż błędnie założyć, że wie, co robi Add.

  3. Inne operacje zapożyczone z języków funkcjonalnych często zachowują swoje oryginalne nazwy, bez widocznych katastrof. Nie widziałem żadnego nacisku na wymyślenie synonimów „map” i „redukować”, które brzmiałyby lepiej dla osób spoza FP, ani nie widzę w tym żadnej korzyści.

(Pełne ujawnienie: jestem programistą Lisp, więc już wiem, co oznaczają minusy).


16
Nie zapominaj, że wykrywalność jest ważna - jeśli wpiszesz „testSuite”. i spójrz na listę metod, chciałbym zobaczyć metodę, która sugeruje właściwą rzecz. Prawdopodobnie nie będę szukał nonsensownego (dla mnie) nazwiska na offie, to jest to, czego chcę.
Jon Skeet

33
Gdybym był wulgarny, stworzyłbym metodę Add, która po prostu zgłosiła wyjątek z komunikatem = "Użyj minusów, aby przejść do ImmutableList". :-)
Ken

2
Nie zabije nikogo, kto przeczyta kilka wierszy komentarzy wyjaśniających, dlaczego tak się nazywa, i odnosząc je, powiedzmy, do „Struktury i interpretacji programów komputerowych” Abelsona i Sussmana.
John R. Strohm,

16
Tradycyjnie minusy dodają na początku, a nie na końcu. Jest to więc potencjalnie myląca nazwa opisywanej metody.
walkytalky

16
Aby zapisać kolejnego niefunkcjonalnego programistę kliknięciem lub 3 ... en.wikipedia.org/wiki/Cons od słowa „konstrukcja”, wyrażenie „Cons x na y” oznacza zbudowanie nowego obiektu za pomocą ( minus xy)
Myster

59

Właściwie to lubię And, szczególnie w sposób idiomatyczny. Szczególnie chciałbym, jeśli masz statyczną właściwość tylko do odczytu dla pustej listy i być może ustawisz konstruktor na prywatny, abyś zawsze musiał budować z pustej listy.

var list = ImmutableList<string>.Empty.And("Hello")
                                      .And("Immutable")
                                      .And("Word");

Podoba mi się pusty pomysł. Wciąż nie jestem przekonany I choć To tylko trochę bla.
Jon Skeet

To po prostu bardziej przypomina myślenie o stworzeniu listy rzeczy w języku naturalnym. Jeśli przeczytasz to na głos, wydaje się to bardziej intuicyjne niż Add lub Plus. „lista” to pusta lista oraz „cześć” i „niezmienny” i „słowo”. Zgodziłbym się jednak, że w izolacji jest to mniej jasne.
tvanfosson

52

Za każdym razem, gdy mam problem z nomenklaturą, natrafiam na interweny.

thesaurus.com zwraca to dla „dodaj”:

Definicja: przylega, wzrost; skomentuj

Synonimy: afiks, aneks, ante, append, augment, beef up, boost, build up, charge up, kontynuuj, cue in, figure, body out, podgrzewanie, hike, hike up, hitch on, hook on, hook up z, obejmują, jack up, jazz up, połącz ze sobą, pad, parlay, piggyback, podłącz do, polej, odpowiedz, biegnij, powiedz dalej, uderz, snowball, zupa, przyspieszenie, skok, skok, uzupełnienie, słodzenie, przybijanie, tag

Lubię dźwięk Adjoinlub prościej Join. To właśnie robisz, prawda? Ta metoda może również dotyczyć łączenia się z innymi ImmutableList<>.


1
Ja też trochę lubię „Połącz”, ale w większości przypadków, gdy łączysz 2 obiekty, to kończysz się na 1. W tym przypadku, jeśli łączysz 2 obiekty, tak naprawdę tworzysz nowy obiekt.
Outlaw Programmer

1
Nie znam żadnych przypadków w .NET, Perl, PHP, a nawet VBScript, w których Join oznacza mutację. Projekt jest taki, że A i B łączą się, tworząc C, gdzie C jest zawsze nowym bytem.
spoulson

Lubię dołączać i całkowicie zgadzam się z thesaurus.com :) Używaj go przez cały czas, gdy masz wątpliwości co do imienia.
Skurmedel

var suite = new TestSuite <string, int> () .Join (x => x.Length);
Sly Gryphon,

3
Idź z Piggyback, imo. Lub HookUpWith.
Chris Marasti-Georg,

48

Osobiście lubię .With (). Gdybym używał tego obiektu, po przeczytaniu dokumentacji lub komentarzy do kodu, byłoby jasne, co robi, i czyta ok w kodzie źródłowym.

object.With("My new item as well");

Lub dodajesz „Along”… :)

object.AlongWith("this new item");

+1. „Z” to operator infix w SETL, który robi to samo. Jeśli SETL go używa, to musi mieć rację :-)
finnw

1
Bah ... Kto już korzysta z VB? :) Uh .. Opps .. Czy to było głośno? heh .. Ale nie, z całą powagą, dlatego pomyślałem o „AlongWith”, który usunąłby problem VB. Jest tylko około miliona różnych sposobów, w jakie mógłby się z tym pogodzić ... Mam na myśli, nawet szalone, takie jak: object.Plus () lub Object.ExistingPlus () ... itd. ... To cholernie dobre pytanie opublikował jednak ... heh ..
LarryF

33

Skończyłem z Add dla wszystkich moich niezmiennych kolekcji w BclExtras . Powodem jest to, że jest to łatwa do przewidzenia nazwa. Nie martwię się tak bardzo o ludzi mylących Add z dodawaniem mutującym, ponieważ nazwa typu jest poprzedzona Immutable.

Przez chwilę rozważałem Wady i inne funkcjonalne nazwy stylów. Ostatecznie zdyskontowałem je, ponieważ nie są tak dobrze znane. Na pewno programiści funkcjonalni zrozumieją, ale nie stanowią większości użytkowników.

Inne nazwy: wspomniałeś:

  • Plus: mam życzenie / obmywam to. Dla mnie nie oznacza to, że jest to operacja niemutująca, niż Add
  • Z: spowoduje problemy z VB (gra słów zamierzona)
  • Przeciążenie operatora: Problemem może być wykrywalność

Opcje, które rozważałem:

  • Concat: Stringi są niezmienne i używaj tego. Niestety jest naprawdę dobry do dodania do końca
  • CopyAdd: co skopiować? Źródło, lista?
  • AddToNewList: Może dobry dla listy. Ale co z kolekcją, stosem, kolejką itp.

Niestety tak naprawdę nie wydaje się, że jest to słowo

  1. Zdecydowanie niezmienna operacja
  2. Zrozumiałe dla większości użytkowników
  3. Reprezentatywne w mniej niż 4 słowa

Staje się to jeszcze bardziej dziwne, gdy weźmie się pod uwagę kolekcje inne niż Lista. Weźmy na przykład Stack. Nawet programiści z pierwszego roku mogą powiedzieć, że stosy mają parę metod Push / Pop. Jeśli utworzysz ImmutableStack i nadasz mu zupełnie inną nazwę, nazwijmy go Foo / Fop, właśnie dodałeś więcej pracy, aby mogli korzystać z Twojej kolekcji.

Edycja: Odpowiedź na edycję Plus

Widzę, gdzie idziesz z Plus. Myślę, że silniejszym przypadkiem byłby minus do usunięcia. Gdybym zobaczył poniższe, z pewnością zastanawiałbym się, co na świecie myślał programista

list.Minus(obj);

Największym problemem, jaki mam z Plus / Minus lub nowym parowaniem, jest odczucie przesady. Sama kolekcja ma już wyróżniającą nazwę, niezmienny przedrostek. Po co dalej, dodając słownictwo, którego celem jest dodanie takiego samego rozróżnienia, jak w przypadku przedrostka Immutable.

Widzę argument strony połączenia. Sprawia, że ​​jest wyraźniejszy z punktu widzenia pojedynczego wyrażenia. Ale w kontekście całej funkcji wydaje się to niepotrzebne.

Edytuj 2

Zgadzam się, że ludzie zdecydowanie zostali zdezorientowani przez String.Concat i DateTime.Add. Widziałem kilku bardzo błyskotliwych programistów, którzy dotknęli tego problemu.

Myślę jednak, że ImmutableList to inny argument. Nie ma nic w String ani DateTime, które ustalałyby go jako niezmienny dla programisty. Musisz po prostu wiedzieć , że jest niezmienny przez inne źródło. Tak więc zamieszanie nie jest nieoczekiwane.

ImmutableList nie ma tego problemu, ponieważ nazwa określa jego zachowanie. Można argumentować, że ludzie nie wiedzą, czym jest Niezmienny i myślę, że to również jest ważne. Z pewnością nie wiedziałem o tym aż do około 2 roku na studiach. Ale masz ten sam problem z dowolną nazwą, którą wybierzesz zamiast Dodaj.

Edycja 3: Co z typami takimi jak TestSuite, które są niezmienne, ale nie zawierają słowa?

Myślę, że to prowadzi do pomysłu, że nie powinieneś wymyślać nowych nazw metod. Mianowicie dlatego, że istnieje wyraźna potrzeba uczynienia typów niezmiennymi w celu ułatwienia operacji równoległych. Jeśli skupisz się na zmianie nazw metod dla kolekcji, następnym krokiem będzie mutacja nazw metod na każdym używanym typie, który jest niezmienny.

Myślę, że bardziej wartościowym wysiłkiem byłoby skupienie się na tworzeniu typów identyfikowalnych jako Niezmienne. W ten sposób możesz rozwiązać problem bez ponownego przemyślenia każdego wzoru metody mutacji.

Jak możesz teraz zidentyfikować TestSuite jako Niezmienny? W dzisiejszym środowisku myślę, że jest kilka sposobów

  1. Prefiks z Immutable: ImmutableTestSuite
  2. Dodaj atrybut, który opisuje poziom Immutablitiy. Jest to z pewnością mniej odkrywalne
  3. Niewiele więcej.

Domyślam się / mam nadzieję, że narzędzia programistyczne zaczną pomagać temu problemowi, ułatwiając identyfikację niezmiennych typów po prostu na podstawie wzroku (inny kolor, mocniejsza czcionka itp.). Myślę jednak, że to jest odpowiedź na zmianę wszystkich nazw metod.


Dodałem powody mojej preferencji dla Plus zamiast Dodaj do pytania. Mile widziane są komentarze do tego rozumowania. Jestem naprawdę otwarty na pomysł, że jestem głupi.
Jon Skeet

@JaredPar: A jeśli typ nazywa się TestSuite?
Jon Skeet

Chciałbym dać kolejne +1 za edycję 3, ale oczywiście nie mogę. (W moim przypadku nie próbuję uzyskać równoległość - Wierzę, że prowadzi niezmienność kod, który jest łatwiejszy do rozumu ok.) Nadal nie jestem całkiem przekonany, że dodawania jest droga, ale wsparcie w odpowiedzi tutaj jest przekonujący.
Jon Skeet

Jeśli zdołasz upuścić to pytanie w rozmowie w pracy, to też byłoby niesamowite, btw. Rozpocząłem dyskusję na liście C #, więc niektórzy koledzy mogą już być zaangażowani. Może też zapytać wewnętrznie w Google ...
Jon Skeet

@Jon, myślę, że znam dobre miejsce, aby zostawić to pytanie w pracy :)
JaredPar

27

Myślę, że może to być jedna z tych rzadkich sytuacji, w których dopuszczalne jest przeciążenie +operatora. W terminologii matematycznej wiemy, że +nie dołącza czegoś do końca czegoś innego. Zawsze łączy dwie wartości razem i zwraca nową wartość wynikową.

Na przykład intuicyjnie oczywiste jest, że kiedy mówisz

x = 2 + 2;

wynikowa wartość x wynosi 4, a nie 22.

Podobnie,

var empty = new ImmutableList<string>();
var list1 = empty + "Hello";
var list2 = list1 + "immutable";
var list3 = list2 + "word";

powinien wyjaśnić, co będzie przechowywać każda zmienna. Powinno być jasne, że list2nie zmienia się w ostatnim wierszu, ale zamiast tego list3przypisywany jest wynik dodawania „słowa” do list2.

W przeciwnym razie nazwałbym funkcję Plus ().


Co jeśli masz ogólną listę, do której możesz legalnie dodać liczbę całkowitą (powiedzmy) lub inną listę liczb całkowitych? Wtedy użycie + spowoduje konflikt z konkatenacją.
finnw

@finnw: Nie rozumiem, o co ci chodzi. Z listą zawsze spodziewałbym się, że + będzie oznaczać dołączenie, niezależnie od tego, czy dodajesz jeden element, czy wiele.
Bill the Lizard

4
W przypadku interfejsu API wielokrotnego użytku dobrze jest mieć także nazwaną metodę, na wypadek, gdyby ktoś używał twojej klasy z języka bez przeciążania operatora.
Neil

„czy dodajesz jeden element czy wiele” - co jeśli chcesz dodać listę jako pojedynczy element? tj . [1] + 2 = [1,2]ale [1] + [2] = [1,[2]]. Sugerujesz, że zachowanie jest niespójne. Prawdopodobnie dlatego Python nie pozwala na dodanie jednego elementu w ten sposób.
mpen

Cóż, w statycznie typowanym języku, takim jak C #, możesz powiedzieć, jaki jest typ elementów listy, czy to lista list, czy lista elementów, więc możesz użyć tego typu, aby zdecydować, czy dodać jeden element, który jest listą, czy konkatenować dwie listy.
Dobes Vandermeer

22

Aby być jak najbardziej klarownym, możesz chcieć iść z wordier CopyAndAddlub czymś podobnym.


Jednak nie wszystkie niezmienne kolekcje wymagają dodania kopii. Pomyśl o niezmiennych drzewach, potrzebują tylko nowego węzła i mogą użyć istniejącego drzewa jako liścia.
JaredPar

BTW, przeczytałem twój profil, potem uporządkowałem wiek i byłem krótko zaskoczony, ile masz lat. Wkrótce potem doszło do szaleństwa.
JaredPar,

Pierwszy komentarz: Używam drzew tak rzadko, że ta myśl nigdy nie przyszła mi do głowy. To dobra uwaga.
Michael Myers

Re drugi komentarz: Przyznaję, zrobiłem to dla odznaki! Po prostu nie lubię podawać swojego wieku. (Właściwie jestem młodszy od ciebie, ale mogę grać w crotche old-timer w razie potrzeby. I nie jestem jedynym, którego wiek jest wymieniony na 89.)
Michael Myers

Argument o niezmiennych drzewach jest mocny, słuszny. Zatem EquivalentReferenceWithAdded (x) walczyłby z tym argumentem, ale brzmi głupio i trudno go wydedukować.
TheBlastOne,

21

Nazwałbym to Extend (), a może ExtendWith (), jeśli masz ochotę na gadatliwość.

Rozszerza oznacza dodawanie czegoś do czegoś innego bez zmiany. Myślę, że jest to bardzo istotna terminologia w języku C #, ponieważ jest podobna do koncepcji metod rozszerzenia - „dodają” nową metodę do klasy bez „dotykania” samej klasy.

W przeciwnym razie, jeśli naprawdę chcesz podkreślić, że w ogóle nie modyfikujesz oryginalnego obiektu, użycie jakiegoś przedrostka, takiego jak Get, wydaje mi się nieuniknione.


1
Nadal implikuje zrobienie czegoś z obiektem podstawowym.
chaos

18

Dodano (), dołączono ()

Lubię używać czasu przeszłego do operacji na obiektach niezmiennych. Przedstawia ideę, że nie zmieniasz oryginalnego obiektu i łatwo go rozpoznać, gdy go zobaczysz.

Ponadto, ponieważ nazwy metod mutacji są często czasownikami teraźniejszymi, ma to zastosowanie do większości przypadków, w których natrafisz na niezmienne metody. Na przykład niezmienny stos ma metody „wypychane” i „wyskakujące”.


1
Python również korzysta z tej konwencji. Np. Wbudowane funkcje reverse () i sorted ().
Joe

18

Lubię sugestię mmyers dotyczącą CopyAndAdd . Zgodnie z motywem „mutacji”, możesz wybrać Bud (rozmnażanie bezpłciowe), Grow , Replicate lub Evolve ? =)

EDYCJA: Aby kontynuować z moim motywem genetycznym, co powiesz na Procreate , sugerując, że powstaje nowy obiekt, który jest oparty na poprzednim, ale z czymś nowym.


14

Jest to prawdopodobnie odcinek, ale w Ruby powszechnie stosuje się oznaczenie rozróżnienia: addnie mutuje; add!mutuje Jeśli jest to wszechobecny problem w twoim projekcie, możesz to zrobić (niekoniecznie za pomocą znaków niealfabetycznych, ale konsekwentnie używając notacji do wskazania metod mutacji / niemutacji).


+1. nie jest to egzekwowane przez język, ale uzgodnione jako konwencja. Lubię to.
oma


13

Być może zamieszanie wynika z faktu, że chcesz dwóch operacji w jednym. Dlaczego ich nie rozdzielić? Styl DSL:

var list = new ImmutableList<string>("Hello");
var list2 = list.Copy().With("World!");

Copyzwróci obiekt pośredni, czyli zmienną kopię oryginalnej listy. Withzwróci nową niezmienną listę.

Aktualizacja:

Ale posiadanie pośredniej, zmiennej kolekcji wokół nie jest dobrym podejściem. Obiekt pośredni powinien być zawarty w Copyoperacji:

var list1 = new ImmutableList<string>("Hello");
var list2 = list1.Copy(list => list.Add("World!"));

Teraz Copyoperacja wymaga delegata, który otrzymuje zmienną listę, aby mógł kontrolować wynik kopiowania. Może zrobić znacznie więcej niż dodawanie elementu, takie jak usuwanie elementów lub sortowanie listy. Można go również użyć w ImmutableListkonstruktorze do złożenia początkowej listy bez pośrednich niezmiennych list.

public ImmutableList<T> Copy(Action<IList<T>> mutate) {
  if (mutate == null) return this;
  var list = new List<T>(this);
  mutate(list);
  return new ImmutableList<T>(list);
}

Teraz nie ma możliwości błędnej interpretacji przez użytkowników, naturalnie wpadną w pułapkę sukcesu .

Kolejna aktualizacja:

Jeśli nadal nie podoba ci się wzmianka o liście zmiennych, nawet teraz, gdy jest ona zawarta, możesz zaprojektować obiekt specyfikacji, który określi lub skrypt , w jaki sposób operacja kopiowania przekształci jego listę. Użycie będzie takie samo:

var list1 = new ImmutableList<string>("Hello");
// rules is a specification object, that takes commands to run in the copied collection
var list2 = list1.Copy(rules => rules.Append("World!"));

Teraz możesz być kreatywny dzięki nazwom reguł i możesz ujawniać tylko funkcje, które chcesz Copyobsługiwać, a nie wszystkie możliwościIList .

Na potrzeby łączenia w łańcuch można utworzyć rozsądnego konstruktora (który oczywiście nie będzie używał tworzenia łańcuchów):

public ImmutableList(params T[] elements) ...

...

var list = new ImmutableList<string>("Hello", "immutable", "World");

Lub użyj tego samego delegata w innym konstruktorze:

var list = new ImmutableList<string>(rules => 
  rules
    .Append("Hello")
    .Append("immutable")
    .Append("World")
);

Zakłada się, że rules.Appendmetoda zwracathis .

Oto jak wyglądałoby to na twoim najnowszym przykładzie:

var suite = new TestSuite<string, int>(x => x.Length);
var otherSuite = suite.Copy(rules => 
  rules
    .Append(x => Int32.Parse(x))
    .Append(x => x.GetHashCode())
);

@Jordao: Ponieważ ten rodzaj kompozycji prowadzi do znacznie prostszej formy inicjalizacji. Dlaczego dwie oddzielne zmienne, kiedy chcę tylko jedną? Podobnie nie chcę tworzyć modyfikowalnej kopii - chcę, aby wszystko było niezmienne przez cały czas, ponieważ prowadzi to do kodu, który jest łatwiejszy do zrozumienia, IMO.
Jon Skeet,

Wszystkie z nich są nadal bardziej nieporęczne niż „plus” itp. Dzięki za pomysły dotyczące mutacji, ale zdecydowanie wolę podejście „utrzymywanie niezmienności”.
Jon Skeet,

Może wtedy będą błędne interpretacje prawdziwej semantyki operacji. Jeśli podążysz ścieżką obiektu specyfikacji, nie nastąpi żadna mutacja (przynajmniej nie zewnętrznie), obiekt określa jedynie, jak będzie wyglądał nowy niezmienny obiekt. To, co masz, to operacja, która przechodzi z jednego niezmiennego obiektu do drugiego, a ponieważ nazywa się Kopiuj, nie ma miejsca na nieporozumienia.
Jordão

Wydaje się, że jedyne, które by Ci pasowały, to nazwy złożone, takie jak CopyAndAppend, CopyAndAdd itp.
Jordão

1
Właściwie to coraz bardziej podoba mi się to podejście (wraz ze wszystkimi zmianami) jako interfejs API dla niezmiennych kolekcji. Argumentowałbym jednak, że połączenie tego podejścia z metodami pomocniczymi dla domyślnych specyfikacji, takich jak Concat, oferuje to, co najlepsze z obu światów.
konopie

11

Kilka przypadkowych myśli:

  • ImmutableAdd ()
  • Dodać()
  • ImmutableList <T> (ImmutableList <T> originalList, T newItem) Konstruktor

1
+1 dla ImmutableAdd; nie lubię Append (zbyt mocno jak StringBuilder.Append, który mutuje), a wersja konstruktora jest utrudniona pod względem łączenia.
Jon Skeet

10

DateTime w C # używa Add. Dlaczego więc nie użyć tej samej nazwy? Tak długo, jak użytkownicy twojej klasy rozumieją, że klasa jest niezmienna.


Twierdziłbym, że DateTime.Add jest znany z mylenia ludzi ... ale zgadzam się, że to ma pierwszeństwo.
Jon Skeet

1
Podobnie jak metody mutowania łańcucha są mylące dla nowych programistów. Ale wkrótce wszyscy dowiadują się, że „struny są niezmienne”
Tundey,

9

Myślę, że kluczową rzeczą, na którą próbujesz się zdobyć, a która jest trudna do wyrażenia, jest brak zgody, więc może coś ze słowem generatywnym, coś takiego jak CopyWith () lub InstancePlus ().


9

Nie sądzę, aby język angielski pozwolił ci sugerować niezmienność w jednoznaczny sposób, używając czasownika, który oznacza to samo, co „Dodaj”. „Plus” prawie to robi, ale ludzie wciąż mogą popełnić błąd.

Jedynym sposobem, aby zapobiec pomyleniu obiektu przez użytkownika z czymś zmiennym, jest podanie go jawnie, albo poprzez nazwę samego obiektu, albo poprzez nazwę metody (jak w przypadku pełnych opcji, takich jak „GetCopyWith” lub „CopyAndAdd”).

Wybierz swój ulubiony „Plus”.


9

Po pierwsze, ciekawy punkt wyjścia: http://en.wikipedia.org/wiki/Naming_conventions_(programming) ... W szczególności sprawdź linki „Zobacz także” u dołu.

Jestem zwolennikiem Plus lub And, w równym stopniu skutecznie.

Plus i And są oparte na matematyce w etymologii. Jako takie, oba odnoszą się do operacji matematycznych; oba dają wyrażenie, które w naturalny sposób odczytuje jako wyrażenia, które mogą przekształcić się w wartość, która pasuje do metody o wartości zwracanej. Andma dodatkową logiczną konotację, ale oba słowa intuicyjnie odnoszą się do list. Addoznacza akcję wykonywaną na obiekcie, który jest sprzeczny z niezmienną semantyką metody.

Oba są krótkie, co jest szczególnie ważne, biorąc pod uwagę prymitywność operacji. Proste, często wykonywane operacje zasługują na krótsze nazwy.

Wyrażanie niezmiennej semantyki to coś, co wolę robić przez kontekst. Oznacza to, że wolałbym po prostu sugerować, że cały ten blok kodu ma funkcjonalny charakter; zakładamy, że wszystko jest niezmienne. To może być tylko ja. Wolę niezmienność niż regułę; jeśli jest zrobione, robi się dużo w tym samym miejscu; zmienność jest wyjątkiem.


8

Co powiesz na Chain () lub Attach ()?


1
Dołącz z pewnością brzmi jak mutacja. Może WithAttached byłby mądrzejszy, ale jest bardzo zbliżony do WithAdded, co zostało omówione powyżej.
TheBlastOne,

To zabawne, ile razy wspomniano o „łańcuchach” w opisie tego, co próbuje się z tym zrobić. (5 razy), ale nie jest to propozycja! To pierwsza rzecz, jaka się pojawiłavisualthesaurus.com (brak powiązania), kiedy szukałem concatenate.
cod3monk3y

7

Wolę Plus (i Minus). Są łatwo zrozumiałe i bezpośrednio odwzorowują operacje obejmujące dobrze znane niezmienne typy (liczby). 2 + 2 nie zmienia wartości 2, zwraca nową, równie niezmienną wartość.

Niektóre inne możliwości:

Splatać()

Zaszczepić()

Narastać()


6

Jak o koledze , mateWith lub coitus , dla tych, którzy pozostają. Pod względem rozmnażania ssaki są ogólnie uważane za niezmienne.

Zamierzam też tam wyrzucić Unię . Pożyczone z SQL.


2
+1 dla Unii. Pożyczam to jednak bezpośrednio od teorii mnogości. :-)
Christoffer Lette

Czy SQL nie korzysta w dużej mierze ze swojej terminologii z teorii mnogości?
Jason D

1
Również Union implikuje rozróżnienie. Jeśli chcesz dołączyć duplikaty (przynajmniej w TSQL), musisz jawnie używać Union All.
konopie

6

Najwyraźniej jestem pierwszą osobą Obj-C / Cocoa, która odpowiedziała na to pytanie.

NNString *empty = [[NSString alloc] init];
NSString *list1 = [empty stringByAppendingString:@"Hello"];
NSString *list2 = [list1 stringByAppendingString:@"immutable"];
NSString *list3 = [list2 stringByAppendingString:@"word"];

Dzięki temu nie wygrasz żadnych gier w golfa z kodem.


[object]By[Verb]ing[Object]:Prefiks +1 metody oznacza, że ​​zostanie zwrócony nowy ciąg, a nie zwykły [verb][Object]:, co zmutowałoby obiekt w miejscu.
Dave DeLong

3
+1. Nie rozumiem niechęci ludzi do gadatliwości (aka samodokumentacji).
hatfinch

5

Myślę, że „Dodaj” lub „Plus” brzmi dobrze. Nazwa samej listy powinna wystarczyć, aby przekazać niezmienność listy.


To nie jest lista, to zestaw testowy.
tstenner

5

Być może są słowa, które pamiętają mnie bardziej o robieniu kopii i dodawaniu do niej rzeczy zamiast mutowania instancji (np. „Concatenate”). Ale myślę, że warto mieć trochę symetrii tych słów w przypadku innych działań. Nie znam podobnego słowa „usuń”, które myślę o tym samym rodzaju, co „konkatenat”. „Plus” brzmi dla mnie trochę dziwnie. Nie spodziewałbym się, że będzie on używany w kontekście nienumerycznym. Ale to również może pochodzić z mojego nieanglojęzycznego pochodzenia.

Może użyłbym tego schematu

AddToCopy
RemoveFromCopy
InsertIntoCopy

Mają jednak własne problemy, kiedy o tym myślę. Można by pomyśleć, że coś usuwają lub dodają coś do podanego argumentu. Wcale nie jestem tego pewien. Sądzę, że te słowa również nie pasują do tworzenia łańcuchów. Zbyt trudny do pisania.

Może po prostu użyłbym zwykłego „Dodaj” i znajomych. Podoba mi się, jak się go stosuje w matematyce

Add 1 to 2 and you get 3

Cóż, z pewnością 2 pozostaje 2 i dostajesz nowy numer. Chodzi o dwie liczby, a nie o listę i element, ale myślę, że ma pewną analogię. Moim zdaniem addniekoniecznie oznacza to, że coś mutujesz. Z pewnością rozumiem, że posiadanie samotnej instrukcji zawierającej tylko addi nieużywanie zwróconego nowego obiektu nie wygląda na błędne. Ale teraz zastanawiałem się nad pomysłem użycia innej nazwy niż „dodaj”, ale po prostu nie mogę wymyślić innej nazwy, nie zmuszając mnie do myślenia „hmm, musiałbym przejrzeć dokumentację, aby wiedzieć, co chodzi o „ponieważ jego nazwa różni się od tego, co miałbym nazywać„ dodać ”. Tylko trochę dziwnych myśli na ten temat z litba, nie jestem pewien, czy to w ogóle ma sens :)


Ale kopia nie zawsze jest konieczna w niezmiennej kolekcji. Weźmy na przykład drzewo binarne. Dodanie nowego katalogu głównego nie wymaga kopiowania, tylko nowa wartość, w której jednym z liści jest stare drzewo
JaredPar

KISS - Gdyby nazewnictwo zaczęło zawierać szczegóły implementacji, wszystkie nazwy metod stałyby się zbyt długie. „dodaj” jest dość proste i działa.
mP.

1
@ MP: Ponownie używasz wyrażenia „szczegóły implementacji” w sposób, który moim zdaniem jest nieodpowiedni. To, czy jest to lista połączona, czy tablica pod maską, jest szczegółem implementacji . Mogę zmienić implementację bez zmiany interfejsu API. Niezmienny aspekt nie jest szczegółem implementacji.
Jon Skeet

1
dobrze. JaredPar, ale myślę, że nie jest ważne, czy faktycznie kopiuje drzewo, czy po prostu używa istniejącego drzewa i używa go jako liścia dla zwróconego nowego drzewa. mam na myśli, że to tylko szczegół implementacji. jest to samo dla (jak sądzę) operacji na podciągu dla klasy łańcuchowej java.
Johannes Schaub - litb


5

Myślę, że Plus()i Minus()lub, alternatywnie Including(), Excluding()są uzasadnione w sugerując niezmienne zachowanie.

Jednak żaden wybór nazewnictwa nie sprawi, że będzie to całkowicie jasne dla wszystkich, więc osobiście uważam, że dobry komentarz do dokumentu xml przeszedłby tutaj bardzo długą drogę. VS rzuca ci prosto w twarz, kiedy piszesz kod w IDE - trudno je zignorować.


4

Append - ponieważ pamiętaj, że nazwy System.String metod sugerują, że mutują one instancję, ale nie robią tego.

Lub lubię AfterAppending:

void test()
{
  Bar bar = new Bar();
  List list = bar.AfterAppending("foo");
}

Podobnie jak w przypadku argumentu DateTime wcześniej, zgodziłbym się z pierwszeństwem, gdyby nie fakt, że tak wielu programistów spodziewa się , że metody łańcuchowe będą wykonywać różne czynności. Należy im powiedzieć (czasem wielokrotnie), że ciągi są niezmienne. Chciałbym zwabić programistów moich klientów w pułapkę sukcesu :)
Jon Skeet

Lubię Append coraz bardziej. Wyróżnia to, że nie modyfikujesz kolekcji, ale dołączasz do niej.

4

list.CopyWith(element)

Podobnie jak Smalltalk :)

A także list.copyWithout(element)usuwa wszystkie wystąpienia elementu, co jest najbardziej przydatne, gdy jest używany list.copyWithout(null)do usuwania nieuzbrojonych elementów.


3

Wybrałbym Add, ponieważ widzę zaletę lepszej nazwy, ale problemem byłoby znalezienie różnych nazw dla każdej innej niezmiennej operacji, co może sprawić, że klasa jest dość nieznana, jeśli ma to sens.


Tak, rozumiem twój punkt widzenia. Myślę, że nadal wolę Plus, ale warto o tym pomyśleć.
Jon Skeet

Dzięki Jon. Nie mogę się doczekać korzystania z twojego frameworka. Właśnie o tym czytałem i wygląda bardzo dobrze. Dzięki jeszcze raz.
Joan Venge
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.