Как лучше всего назвать не мутирующий метод «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 ]

0 голосов
/ 06 февраля 2009

Я бы пошел на простой Add (). Альтернативой будет Append (), если вы хотите передать, что это действительно операция сбора.

В дополнение к явному методу я бы по-прежнему предлагал реализовать obverloaded + operatr. Это хорошо известная операция. Все знают, что String неизменна, но все используют «+» для создания новых экземпляров.

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

.Trail подразумевает, что очень четкое понимание списка не изменилось, этот объект отстает от списка, он не был добавлен к нему.

var list = new ImmutableList<string>().Trail("Hello");
                                      .Trail("immutable");
                                      .Trail("word");
0 голосов
/ 03 марта 2011

Проблема с такими методами, как String.Replace, заключается в том, что пользователь может ошибочно думать, что происходит мутация, поскольку он может игнорировать возвращаемое значение. Поэтому используйте параметр out для возвращаемого значения. Пользователь не может игнорировать , что :

public class ImmutableList<T> {
  public ImmutableList(params T[] items) { 
    // ...
  }
  /// <summary>
  /// Creates a new list with the items in this list plus the supplied items.
  /// </summary>
  /// <param name="newList">
  /// The new list created for the operation.
  /// This is also the return value of this method.
  /// </param>
  /// <param name="items">Items to add to the new list.</param>
  /// <returns>The new list.</returns>
  public ImmutableList<T> Add(out ImmutableList<T> newList, params T[] items) {
    // ...
  }
}

Использование:

var list = new ImmutableList<string>("Hello", "Immutable");
ImmutableList<string> newList;
list.Add(out newList, "World");
0 голосов
/ 06 февраля 2009

Мне нравится А () . Я думаю, что это имеет наименьший потенциал для двусмысленности. Единственное столкновение, о котором я могу думать, - это логическое И, я не вижу, чтобы это было проблемой для разработчика на C # и даже для VB, я думаю, что контекст делает его маловероятным, чтобы вызвать проблему, и любая проблема будет быстро решена в время компиляции. Он также хорошо работает на английском языке «Сделай что-нибудь с этим и этим» или «Положи это и это в коробку».

Я думаю .With () в порядке . Меня беспокоит, что это может выглядеть немного как метод linq Where <>, особенно если в качестве аргумента есть лямбда-выражение. Английский в моей голове также менее ясен, особенно "Делай что-нибудь с этим".

Мне не нравится .Plus () . Я не могу пропустить это как синоним для Add: знак плюс = плюс = + = добавить.

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

"add ()"

Тот факт, что он возвращает новый список, является несущественным, что является деталью реализации, раскрытой подписью. Основное действие, которое выполняет метод, это «добавление» нового элемента в список. То, как или что он возвращает или возвращает, не должно быть частью имени метода. Если бы метод был синхронизирован, это повлияло бы на имя метода - "synchronizedAdd ()" ??? - конечно нет.

Классы, такие как String, которые следуют шаблону "будущий мутатор", все еще имеют действительно простые имена методов - ни один из них не является составным словом.

0 голосов
/ 07 февраля 2010

Я бы пошел с перегрузкой оператора +. Причина в том, что так работает в Python - .append() в списке изменяет список, а выполнение + с другим списком создает новый список + также определенно не подразумевает мутации. Я думаю, именно поэтому вам так нравится .Plus(), поэтому, если вы не хотите перегружать оператор, вы можете использовать .Plus().

0 голосов
/ 06 февраля 2009

"Заменить"? Он не добавляет в список, он заменяет список новым.

0 голосов
/ 08 октября 2009

Поскольку этот вопрос теперь в основном тезаурус: как насчет .Bring(). Как, дать мне этот список и взять с собой этот элемент?

Foo = List.Bring('short');
          .Bring('longer');
          .Bring('woah');

Это не скатывается с языка, но это означает это, для меня.

На самом деле, AndBring() может быть даже лучше.

0 голосов
/ 03 августа 2009

Как насчет создания класса-оболочки с помощью метода Augment (или AugmentWith)?

0 голосов
/ 26 июня 2009

C # -ish псевдокод следует:

interface Foo
{
    // Constructors
    Foo();
    Foo(params Foo[] foos);

    // Instance method
    Foo Join(params Foo[] foos);

    // Class method
    static Foo Join(params Foo[] foos);
}    

Так что вы можете назвать такие вещи:

var f0 = new Foo();
var f1 = new Foo(new Foo(), new Foo(), new Foo());
var f2 = Foo.Join(new Foo(), new Foo(), new Foo());
var f3 = f0.Join(new Foo(), new Foo(), new Foo());
var f4 = new Foo(new Foo(new Foo()), new Foo(), new Foo(new Foo()));

Etc ....

...