Как лучше всего назвать не мутирующий метод «add» в неизменяемой коллекции? - PullRequest
224 голосов
/ 06 февраля 2009

Извините за вафельный заголовок - если бы я мог придумать краткое название, мне не пришлось бы задавать вопрос.

Предположим, у меня есть тип неизменяемого списка. Он имеет операцию Foo(x), которая возвращает новый неизменный список с указанным аргументом в качестве дополнительного элемента в конце. Таким образом, чтобы создать список строк со значениями «Hello», «immutable», «world», вы можете написать:

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

(Это код на C #, и меня больше всего интересует предложение на C #, если вы считаете, что язык важен. Это не вопрос языка, но и идиомы языка могут быть важны.)

Важно то, что существующие списки не изменены Foo, поэтому empty.Count все равно вернет 0.

Другой (более идиоматический) способ достижения конечного результата:

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

Мой вопрос: Какое имя Foo лучше всего?

РЕДАКТИРОВАТЬ 3 : Как я выясню позже, имя типа может на самом деле не быть ImmutableList<T>, что делает позицию ясной. Вместо этого представьте, что он TestSuite и что он неизменен, потому что весь фреймворк, частью которого он является, неизменен ...

(конец редактирования 3)

Опции, которые я до сих пор придумал:

  • Add: распространено в .NET, но подразумевает мутацию исходного списка
  • Cons: Я считаю, что это нормальное имя в функциональных языках, но не имеет смысла для тех, кто не имеет опыта работы с такими языками
  • Plus: мой любимый до сих пор, это не подразумевает мутации для меня . Очевидно, это также используется в Haskell , но с немного иными ожиданиями (программист на Haskell может ожидать, что он добавит два списка вместе, а не добавит одно значение в другой список).
  • With: согласуется с некоторыми другими неизменными соглашениями, но не имеет такой же «добавляемости» к этому IMO.
  • And: не очень описательный.
  • Перегрузка оператора для +: мне действительно не очень нравится; Я вообще думаю, что операторы должны применяться только к типам более низкого уровня. Я готов быть убежденным, хотя!

Критерии, которые я использую для выбора:

  • Дает правильное представление о результате вызова метода (то есть, что это оригинальный список с дополнительным элементом)
  • делает как можно более ясным, что он не изменяет существующий список
  • Звучит разумно, когда объединены в цепочку, как во втором примере выше

Пожалуйста, попросите более подробную информацию, если я не проясняю себя достаточно ...

РЕДАКТИРОВАТЬ 1: Вот мои аргументы в пользу предпочтения от Plus до Add. Рассмотрим эти две строки кода:

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

На мой взгляд (а это является личным делом) последнее явно глючит - это все равно что писать "х + 5;" как заявление само по себе. Первая строка выглядит нормально, пока вы не помните, что она неизменна. Фактически, то, что оператор плюс сам по себе не изменяет свои операнды, является еще одной причиной, по которой Plus является моим любимым. Без незначительной перегрузки операторов, он по-прежнему дает те же коннотации, которые включают (для меня) не мутирование операндов (или цель метода в этом случае).

РЕДАКТИРОВАТЬ 2: Причины неприязни Add.

Различные ответы эффективны: «Идите с Add. Вот что делает DateTime, и у String есть Replace методы и т. Д., Которые не делают очевидной неизменность». Я согласен - здесь есть приоритет. Тем не менее, я видел много людей, которые звонят DateTime.Add или String.Replace и ожидают мутации . Есть множество вопросов группы новостей (и, вероятно, SO, если я копаюсь), на которые отвечают: «Вы игнорируете возвращаемое значение String.Replace; строки неизменны, возвращается новая строка.»

Теперь я должен раскрыть тонкость вопроса - тип может не на самом деле быть неизменным списком, но другого неизменного типа. В частности, я работаю над платформой для сравнительного анализа, где вы добавляете тесты в комплект, и это создает новый комплект. Может быть очевидно, что:

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

ничего не добьется, но становится лот мрачнее, когда вы меняете его на:

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

Похоже, все должно быть в порядке. Тогда как для меня это делает ошибку более ясной:

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

Вот только прошу:

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

В идеале, я бы хотел, чтобы моим пользователям не говорили, что набор тестов является неизменным. Я хочу, чтобы они упали в пропасть успеха. Это может не возможно, но я бы хотел попробовать.

Я прошу прощения за чрезмерное упрощение исходного вопроса, говоря только о неизменяемом типе списка. Не все коллекции настолько информативны, как ImmutableList<T>:)

Ответы [ 74 ]

3 голосов
/ 30 сентября 2010

Мне лично нравится unite(), так как когда вы объединяете объекты, вы все равно сохраняете их индивидуальность, но объединение их - это отдельная новая сущность. Union аналогичен предложенному, но уже хорошо определен в теории множеств, а unite более многословен и говорит мне, что следуя методу, я получаю новую идею. Я знаю, что уже поздно, но эй не смог найти предложение в списке. Плюс слово напоминает мне о днях старых и объединяющих людей, чтобы идти на войну .. хе-хе

3 голосов
/ 04 июня 2010

Может быть лучше использовать статический метод или оператор (который является статическим). Это снимет ответственность с экземпляров, и пользователи сразу поймут, что операция не принадлежит ни одному из экземпляров. Особенно важно НЕ использовать методы расширения, так как их сходство с методами экземпляра при использовании противоречит цели.

Статический метод может называться Join:

public static class ListOperations {
  public static ImmutableList<T> Join<T>(ImmutableList<T> list, T tail) {
    // ...
  }
  public static ImmutableList<T> Join<T>(T head, ImmutableList<T> list) {
    // ...
  }
  public static ImmutableList<T> Join<T>(ImmutableList<T> list1, ImmutableList<T> list2) {
    // ...
  }
  // substitutes chaining:
  public static ImmutableList<T> Join<T>(ImmutableList<T> list, params T[] tail) {
    // ...
  }
}

// ....

var list = new ImmutableList<string>("Hello");
var list2 = ListOperations.Join(list, "immutable"); // inferred type parameter
var list3 = ListOperations.Join(list2, "world!");

Но я бы предпочел, чтобы в C # было функций без классов . Или, по крайней мере, что-то вроде статического средства импорта Java.

Оператор может быть +:

var list = new ImmutableList<string>("Hello");
var list2 = list + "immutable";
var list3 = list2 + "world!";

Но я бы предпочел использовать что-то еще, например <<, :: или ., что невозможно в C #.

Кроме того, статические элементы выглядят более функциональными, и я думаю, что они лучше подойдут для этого неизменного представления.

2 голосов
/ 17 мая 2011

Как насчет cloneWith?

Причины:
1. Для предложенных имен из одного слова я не буду знать наверняка, что они делают, пока не проверим документацию.
2. Для составных названий, предложенных до сих пор, мне больше нравится cloneWith. Я бы точно знал, что я получал с этим именем метода, он относительно лаконичен, его легко запомнить, и он просто «чувствует себя правильно» 3. Благодаря современному дополнению кода в большинстве инструментов программирования, с более длинными именами работать легче, чем когда-либо прежде.

Хотя краткость во многих случаях добавляет ясности, если вы хотите выбрать, проявите ясность в отношении краткости ...

В действии:

var empty = new ImmutableList<string>();
var list1 = empty.cloneWith("Hello");
var list2 = list1.cloneWith("immutable");
var list3 = list2.cloneWith("word");
2 голосов
/ 21 мая 2011

Имя WithAdded читается хорошо и, на мой взгляд, лучше, чем Added, которое можно прочитать как прилагательное.

var list2 = list1.WithAdded("immutable");

Или для более подробной версии:

var list2 = list1.WithAddedElement("immutable");
2 голосов
/ 23 июня 2011

Я немного опоздал на вечеринку, но я не увидел ответа, который предлагал бы просто не делать этого вообще! Пусть это останется неизменным. Любой метод, который вы добавите, будет открыт для злоупотреблений, когда они поместят его в цикл, что ~ произойдет в конечном итоге. Вместо этого я бы посоветовал использовать вместо этого строитель.

MyImmutableBuilder builder = new MyImmutableBuilder(someImmutable);
MyImmutable modifiedImmultable = builder.add(element1).add(element2).build();

Теперь, в случае ImmutableList, ваш конструктор действительно будет просто List, скорее всего.

Полагаю, все сводится к ... вам действительно нужен простой способ добавить всего один элемент в вашу неизменную коллекцию? Если это обычное явление, вы, вероятно, не хотите использовать неизменяемый объект (вы всегда можете вызвать build() в любое время, когда хотите отправить снимок объекта в его текущем состоянии ...) Если это не распространенный возникновение, то наличие строителя в качестве требования сделать это не было бы существенным препятствием, особенно если было задокументировано, что именно так вы и хотите их строить.

@ supercat - рассмотрите следующий сценарий, который вы описываете:

Stack a = someImmutableStackOfSize(10);
Stack b = a.popAndCopy(); // size 9
Stack c = a.popAndCopy(); // also size 9
// a b and c have the same backing list
Stack d = b.pushAndCopy("node1");
Stack e = c.pushAndCopy("node2");
// because d and e had the same backing list, how will it 
// know that d's last element is node1, but e's last
// element is node2?
2 голосов
/ 21 сентября 2011

Я бы выбрал list.NewList("word"), потому что он описывает, что вы делаете (создаете новый список).

Я должен согласиться, что это не означает, что аргумент добавляется в конец списка, но лично я принадлежу к лагерю лаконичный, если возможно , поэтому я бы предпочел list.NewList("word") свыше list.NewListAfterAppend("word").

2 голосов
/ 07 февраля 2009

2 предложения:

Функция «free»:

Foo f = new Foo(whatever);
Foo fPlusSomething = Foo.Concat(f, something);

Перегрузка конструктора (которая, в некотором смысле, является вариацией темы "free function"):

Foo f = new Foo(whatever);
Foo fPlusSomething = new Foo(f, something);
1 голос
/ 28 августа 2015

Хотя правильное присвоение имени методу может уменьшить вероятность неправильного использования, если была какая-то языковая конструкция, сообщающая компилятору: «Этот метод ничего не делает, кроме как возвращает новый объект, его следует использовать или вызов метода не имеет смысла» компилятор может пожаловаться с ошибкой / предупреждением всякий раз, когда кто-то использует метод в неправильной настройке.

Я заполнил запрос на добавление , и вы можете голосовать, если вы согласны.

1 голос
/ 04 апреля 2009

Любое имя, которое подразумевает, что объект того же типа будет возвращен, должно быть хорошо для использования. Плюс - хорошее название для этого, как будто вы, плюс два объекта, ожидаете, что результат будет возвращен.

Плюс просто не похоже на правильное имя, которое следует использовать в данном случае, поскольку вы добавляете тест в набор тестов.

GetWith () звучит как опция для меня. Или когда-либо GetTypeWith (), где тип, очевидно, тип, который вы используете. Так, например:

var list = new ImmutableList<String>();
var list2 = list.GetWith("First");
var list3 = list2.GetWith("Second");

// OR

var list2 = list.GetListWith("First");

Get означает, что вы получаете список, который уже содержится, а With означает, что вам нужен еще один объект вместе с ним. CopyWith () также соответствует этому критерию.

Непосредственная проблема, которую я вижу с GetWith, заключается в том, что это не так легко угадать. Разработчик хочет добавить комплект, а не получить текущий комплект. Я бы сразу набрал. Добавить и надеюсь, что интеллект показал что-то очень близкое к тому, что я ожидал.

1 голос
/ 25 февраля 2010

Как программист на С ++ и поклонник STL, я выдвинул add_copy. (это также предполагает remove_copy, replace_copy и т. д.)

...