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

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

добавлено (), добавлено ()

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

Кроме того, поскольку имена изменяющихся методов часто являются глаголами настоящего времени, это относится к большинству случаев, требующих неизменного имени метода, с которыми вы столкнулись. Например, в неизменяемом стеке есть методы «push» и «popped».

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

Это, вероятно, растяжка, но в Ruby обычно используется обозначение для различия: add не мутирует; add! мутирует. Если это распространенная проблема в вашем проекте, вы можете сделать это тоже (не обязательно с не алфавитными символами, но последовательно использовать нотацию для обозначения мутирующих / немутирующих методов).

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

Возможно, путаница связана с тем, что вы хотите две операции в одной. Почему бы не разделить их? Стиль DSL:

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

Copy вернул бы промежуточный объект, который является изменяемой копией исходного списка. With вернет новый неизменный список.

Обновление:

Но иметь промежуточную изменяемую коллекцию - не очень хороший подход. Промежуточный объект должен содержаться в операции Copy:

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

Теперь операция Copy принимает делегата, который получает изменяемый список, чтобы он мог контролировать результат копирования. Это может сделать гораздо больше, чем просто добавить элемент, например удалить элементы или отсортировать список. Его также можно использовать в конструкторе ImmutableList для сборки исходного списка без промежуточных неизменяемых списков.

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);
}

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

Еще одно обновление:

Если вам по-прежнему не нравится упоминание об изменчивом списке, даже теперь, когда он содержится, вы можете создать объект спецификации, который будет указывать или сценарий как способ операция преобразует свой список. Использование будет таким же:

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!"));

Теперь вы можете проявлять творческий подход с именами правил, и вы можете предоставлять только те функции, которые вы хотите Copy поддерживать, а не все возможности IList.

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

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

...

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

Или используйте тот же делегат в другом конструкторе:

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

Предполагается, что метод rules.Append возвращает this.

Вот как будет выглядеть ваш последний пример:

var suite = new TestSuite<string, int>(x => x.Length);
var otherSuite = suite.Copy(rules => 
  rules
    .Append(x => Int32.Parse(x))
    .Append(x => x.GetHashCode())
);
13 голосов
/ 07 февраля 2009

Join кажется уместным.

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

Несколько случайных мыслей:

  • ImmutableAdd ()
  • Append ()
  • ImmutableList (ImmutableList originalList, T newItem) Конструктор
10 голосов
/ 06 февраля 2009

DateTime в C # использует Add. Так почему бы не использовать одно и то же имя? Пока пользователи вашего класса понимают, что класс неизменен.

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

Во-первых, интересная отправная точка: http://en.wikipedia.org/wiki/Naming_conventions_(programming) ... В частности, проверьте ссылки «См. Также» внизу.

Я за «Плюс» или «А», по сути одинаково.

Плюс и А являются математическими в этимологии. Таким образом, оба обозначают математическую операцию; оба дают выражение, которое естественно читается как выражения, которые могут преобразовываться в значение, которое соответствует методу, имеющему возвращаемое значение. And несет дополнительную логическую коннотацию, но оба слова применяются к спискам интуитивно. Add обозначает действие, выполняемое над объектом, что противоречит неизменной семантике метода.

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

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

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

Я думаю, что ключевая вещь, которую вы пытаетесь найти, которую трудно выразить, это неперестановка, так что, возможно, что-то с порождающим словом в нем, что-то вроде CopyWith () или InstancePlus ().

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

Я не думаю, что английский язык позволит вам безошибочно подразумевать неизменность при использовании глагола, который означает то же самое, что и «Добавить». «Плюс» почти делает это, но люди все еще могут ошибиться.

Единственный способ запретить пользователям ошибочно принять объект за что-то изменяемое - это сделать его явным либо через имя самого объекта, либо через имя метода (как в случае с подробными опциями типа " GetCopyWith "или" CopyAndAdd ").

Так что просто иди с любимым, "Плюс".

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

Как насчет Chain () или Attach ()?

...