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

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

В таких ситуациях я обычно использую Concat. Это обычно означает, что создается новый объект.

var p = listA.Concat(listB);
var k = listA.Concat(item);
119 голосов
/ 07 февраля 2009

Я бы пошел с минусами по одной простой причине: это означает именно то, что вы хотите.

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

  2. Тот факт, что это было бы "бессмысленно" для людей без опыта FP, на самом деле является большим преимуществом. Как вы указали, все остальные слова, которые вы нашли, уже имеют какое-то значение, и это значение немного отличается или неоднозначно. Новая концепция должна иметь новое слово (или, в данном случае, старое). Я предпочел бы, чтобы кто-то посмотрел определение «против», чем предположил, что он неправильно знает, что делает «Ад».

  3. Другие операции, заимствованные из функциональных языков, часто сохраняют свои первоначальные имена без явных катастроф. Я не видел никакого толчка, чтобы придумать синонимы для «map» и «lower», которые звучат более знакомо не-FPers, и я не вижу никакой выгоды от этого.

(Полное раскрытие: я программист на Лиспе, так что я уже знаю, что означает "Против".)

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

На самом деле мне нравится And, особенно в идиоматическом смысле. Мне бы особенно понравилось, если бы у вас было статическое свойство readonly для пустого списка, и, возможно, вы сделали конструктор закрытым, чтобы вам всегда приходилось строить из пустого списка.

var list = ImmutableList<string>.Empty.And("Hello")
                                      .And("Immutable")
                                      .And("Word");
51 голосов
/ 06 февраля 2009

Всякий раз, когда я нахожусь в варенье с номенклатурой, я попадаю в паутину.

thesaurus.com возвращает значение «add»:

Определение: примыкают, увеличиваются; делать дальнейший комментарий

Синонимы: аффикс, приложение, анте, добавить, увеличить, говядина вверх, повысить, создать, зарядить, продолжать, намекайте, фигурируйте, плоть из, разогреть, походу, походу, заминку, зацепить, зацепить, включить, домкрат вверх, джаз, объединиться, колодки, Parlay, контрейлер, подключи, налей на, ответь, подбегай, говори дальше, шлепай на, снежный ком, суп, ускорение, шип, шаг вперед, добавка, подсластить, прикрепить, тег

Мне нравится звук Adjoin, или, проще, Join. Это то, что ты делаешь, верно? Этот метод может также применяться для присоединения к другим ImmutableList<>.

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

Лично мне нравится .With (). Если бы я использовал объект, после прочтения документации или комментариев к коду, было бы понятно, что он делает, и он хорошо читается в исходном коде.

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

Или вы добавляете «Вместе» вместе с ним ..:)

object.AlongWith("this new item");
33 голосов
/ 06 февраля 2009

В итоге я выбрал «Добавить» для всех моих неизменных коллекций в BclExtras . Причина в том, что это легко предсказуемое имя. Меня не очень беспокоит то, что люди путают Add с мутирующим add, так как имя типа имеет префикс Immutable.

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

Другие имена: Вы упомянули:

  • Плюс: я не хочу этого делать. Для меня это не отличает его от того, что он не является мутирующей операцией больше, чем Add
  • С: вызовет проблемы с VB (каламбур)
  • Перегрузка оператора: проблема с обнаружением будет

Варианты, которые я рассмотрел:

  • Concat: Строки неизменны и используют это. К сожалению, это действительно хорошо только для добавления в конец
  • CopyAdd: скопировать что? Источник, список?
  • AddToNewList: Может быть, хороший для List. Но как насчет коллекции, стека, очереди и т. Д ...

К сожалению, на самом деле, кажется, нет слова, которое

  1. Определенно неизменная операция
  2. Понятно большинству пользователей
  3. Представляется менее чем в 4 словах

Это становится еще более странным, когда вы рассматриваете коллекции, отличные от List. Возьмите, например, стек. Даже программисты первого года могут сказать вам, что в стеках есть пара методов Push / Pop. Если вы создаете ImmutableStack и даете ему совершенно другое имя, назовем его Foo / Fop, вы просто добавили больше работы для них, чтобы использовать вашу коллекцию.

Редактировать: Ответ на Plus Редактировать

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

list.Minus(obj);

Самая большая проблема, с которой я сталкиваюсь с плюсом / минусом или новым парным соединением, - это ощущение, что это излишнее. Сама коллекция уже имеет отличительное имя, неизменный префикс. Зачем идти дальше, добавляя словарь, цель которого состоит в том, чтобы добавить то же различие, что и префикс «Неизменный», уже сделанный.

Я вижу аргумент сайта вызова. Это делает это более понятным с точки зрения одного выражения. Но в контексте всей функции это кажется ненужным.

Редактировать 2

Согласитесь, что людей определенно смутили String.Concat и DateTime.Add. Я видел несколько очень ярких программистов, решивших эту проблему.

Однако я думаю, что ImmutableList - это другой аргумент. В String или DateTime нет ничего, что устанавливало бы его как неизменное для программиста. Вы должны просто знать , что он неизменен через какой-то другой источник. Так что путаница не является неожиданной.

ImmutableList не имеет этой проблемы, потому что имя определяет его поведение. Можно утверждать, что люди не знают, что такое Неизменное, и я думаю, что это также верно. Я, конечно, не знал об этом примерно до 2 года в колледже. Но у вас есть та же проблема с любым именем, которое вы выберете вместо Add.

Редактировать 3: А как насчет таких типов, как TestSuite, которые являются неизменяемыми, но не содержат слова?

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

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

Теперь, как вы можете идентифицировать TestSuite как неизменный? В сегодняшней среде я думаю, что есть несколько способов

  1. Префикс с неизменным: ImmutableTestSuite
  2. Добавить Атрибут, который описывает уровень Иммутаблиций. Это, безусловно, менее обнаруживаемо
  3. Не намного больше.

Я предполагаю / надеюсь, что инструменты разработки начнут помогать этой проблеме, упрощая идентификацию неизменяемых типов просто визуально (другой цвет, более сильный шрифт и т. Д.). Но я думаю, что это ответ на изменение всех имен методов.

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

Я думаю, что это может быть одной из тех редких ситуаций, когда допустимо перегрузить оператор +. В математической терминологии мы знаем, что + не добавляет что-либо к концу чего-то другого. Он всегда объединяет два значения вместе и возвращает новое результирующее значение.

Например, интуитивно очевидно, что когда вы говорите

x = 2 + 2;

результирующее значение x равно 4, а не 22.

Аналогично,

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

должен прояснить, что каждая переменная будет содержать. Должно быть ясно, что list2 не является измененным в последней строке, но вместо этого list3 присваивается результат добавления слова в list2.

В противном случае я бы просто назвал функцию Plus ().

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

Чтобы быть как можно более понятным, вы можете использовать слово CopyAndAdd или что-то подобное.

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

Я бы назвал это Extend () или, может быть, ExtendWith () , если вы чувствуете себя действительно многословным.

Расширение означает добавление чего-либо к чему-то другому без его изменения. Я думаю, что это очень актуальная терминология в C #, поскольку она похожа на концепцию методов расширения - они «добавляют» новый метод в класс, не «прикасаясь» к самому классу.

В противном случае, если вы действительно хотите подчеркнуть, что вы вообще не изменяете исходный объект, использование какого-либо префикса, такого как Get, выглядит для меня неизбежным.

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

Мне нравится предложение mmyers CopyAndAdd . В соответствии с темой «мутации», возможно, вы могли бы пойти с Bud (бесполое размножение), Grow , Replicate или Evolve ? =)

РЕДАКТИРОВАТЬ: Продолжая мою генетическую тему, как насчет Создать , подразумевая, что создан новый объект, основанный на предыдущем, но с добавлением чего-то нового.

...