一个不可变集合上最佳的非变异“添加”方法应该取什么名字?

233

抱歉标题过于冗长,如果我能想出一个简洁的标题,我就不用问这个问题了。

假设我有一个不可变列表类型。它有一个操作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: 与某些其他不可变约定一致,但在我的看法中没有完全相同的“添加性”。
  • And: 描述不是很清楚。
  • +的操作符重载 :我真的不太喜欢这个,我通常认为操作符只应用于较低级别的类型。尽管如此,我愿意被说服!

我选择的标准是:

  • 正确地传达方法调用的结果(即原始列表带有额外元素)
  • 尽可能清楚地表明它不会更改现有列表
  • 在像上面第二个例子中链接在一起时听起来合理

如果我没有说清楚,请多问细节...

编辑1:以下是我更喜欢Plus而不是Add的原因。考虑以下两行代码:

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

在我看来(这是个人观点),后者显然有漏洞——就像单独写“x + 5;”语句一样。第一行看起来似乎没问题,但实际上它是不可变的。事实上,加法运算符本身不会改变其操作数的方式是Plus成为我最喜欢的原因之一。没有运算符重载的略微尴尬,它仍然给出了相同的含义,其中包括(对我而言)不改变操作数(或在这种情况下的目标方法)。 编辑2: 不喜欢Add的原因。
各种答案的效果基本上都是:“使用Add。这就是DateTime所做的,还有StringReplace方法等,这些都没有使不可变性明显。” 我同意——这里有先例。但是,我见过很多人调用 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>那样自描述 :)


36
Uncat是否为狗? - Michael Myers
7
Concat/Condog... 对我来说没问题!;) - gnovice
11
Uncat - 那是一只僵尸猫。 - Erik Forbes
2
@Trap:就 API 流畅性而言,那很糟糕。 - Jon Skeet
2
Clojure使用'conj'来表示conjoin(连接)。有点类似于'cons',但行为对集合进行了多态化处理。 - gtrak
显示剩余44条评论
76个回答

131
在这种情况下,我通常会选择使用 Concat。这通常意味着一个新的对象正在被创建。
var p = listA.Concat(listB);
var k = listA.Concat(item);

3
这是我使用的方式,因为它与System.Linq.Enumerable使用的方式相同——决策已经做出。 :) 我不可能是唯一一个定义了自己的Concat扩展重载,以接受要附加的单个值的人。 - Daniel Earwicker
9
这是在框架内保持命名一致性的正确答案。 - Sam Harwell
除非我漏掉了,System.Linq.Enumerable(以及Linq)只在“sequence + sequence”时使用Concat(),而从未在“item + sequence”时使用。有人可能会期望它将两个列表相加,而不是将单个值添加到另一个列表中,这个问题明确给出了不使用其他原始选项的原因。 - Ssswift

119

我会选择“Cons”,仅仅因为它恰好符合你的意思。

  1. 我非常喜欢在源代码中明确表达我的意思。新手只需查找一次Cons的定义,然后再读取和使用一千次。从长远来看,我发现使用使常见情况更容易的系统更好,即使前期成本略高。

  2. 事实上,对于没有函数式编程经验的人来说,“Cons”是“无意义”的这一点实际上是一个巨大优势。如你所指出的,你找到的所有其他单词都已经有了某种含义,并且那个含义要么稍微有些不同,要么模棱两可。新概念应该有一个新词(或在这种情况下是旧词)。与其让某人错误地假设他知道Add是什么,还不如让他查阅Cons的定义。

  3. 从函数式语言借用的其他操作通常保留其原始名称,似乎并没有什么灾难性事件。我没有看到任何推动起来为“映射”和“归约”寻找听起来更熟悉的非FP词汇,也没有看到这样做的任何好处。

(完全透明:我是Lisp程序员,所以我已经知道Cons的含义。)


16
不要忘记发现性很重要 - 如果我输入"testSuite."并查看方法列表,我希望能看到一个建议正确方法的方法。我可能不会去查找一个对我来说毫无意义的名称,以便碰巧找到我想要的东西。 - Jon Skeet
33
如果我感觉刻薄,我会编写一个名为“Add”的方法,该方法仅会抛出异常,并显示消息=“请使用Cons将元素添加到不可变列表中”。:-) - Ken
2
阅读一些注释,解释名称的原因,并将他们引用到Abelson&Sussman的“计算机程序的构造和解释”中,并不会伤害任何人。 - John R. Strohm
16
传统上,cons 是在开头而不是结尾添加元素。因此,对于描述的这种方法来说,“cons”可能是一个具有误导性的名称。 - walkytalky
16
为了帮助下一个不熟悉编程的程序员节省点击跳转的时间,从“构造”一词中衍生出来的“cons”,表达式“将x添加到y上”意味着用(cons x y)构造一个新对象。 - Myster
显示剩余4条评论

59

实际上,我喜欢And,特别是它的成语用法。如果你有一个静态只读属性代表空列表,那就更好了,也许可以将构造函数设置为私有,这样你就必须始终从空列表构建。

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

我喜欢 Empty 这个想法。但是 And though 还没有让我信服。它有点无聊。 - Jon Skeet
它似乎更像是我用自然语言构建事物列表的思维方式。如果你大声朗读,它比Add或Plus更直观。 "list"是空列表,"Hello"、"Immutable"和"Word"就是其中的元素。虽然孤立起来可能不太清晰,但我同意这一点。 - tvanfosson

52

每当我在命名方面遇到麻烦时,我会上网查找。

thesaurus.com 返回 "add" 的以下内容:

定义: 相邻、增加、发表进一步评论

同义词: 附加、合并、加法、添补、增强、加强、添加、继续、提示、计入、详述、加热、提高、攀升、连接、填充、倍增、背靠背、嵌入、借力、回复、累积、细说、涂抹、滚雪球、改进、注重、加速、增补、使更好、追加

我喜欢 "Adjoin" 这个词的音,或者更简单的 "Join"。 对吧?这种方法也适用于连接其他的 ImmutableList<>


1
我也有点喜欢“Join”,但在大多数情况下,当你连接两个对象时,最终只会得到一个对象。但在这种情况下,如果你连接两个对象,实际上是在创建一个新的对象。 - Tim Frey
1
我不知道在.NET、Perl、PHP甚至VBScript中是否存在Join暗示变异的情况。设计是这样的,A和B连接形成C,其中C始终是一个新实体。 - spoulson
我喜欢使用 Join,并且完全赞同 thesaurus.com :) 当对名称存在疑问时,我总是使用它。 - Skurmedel
var suite = new TestSuite<string, int>().Join(x => x.Length); - Sly Gryphon
我理解你的观点,但是要小心这个:http://mindprod.com/jgloss/unmainnaming.html(第6条)。 - Marc Wittke
3
我建议选择“Piggyback”或“HookUpWith”,这是一个简要的翻译,意思是建议跟随或与某人合作。 - Chris Marasti-Georg

48

就我个人而言,我喜欢使用.With()。如果我正在使用这个对象,在阅读文档或代码注释之后,它会很清楚地知道它的作用,并且在源代码中阅读也没问题。

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

或者,你可以加上 "Along" .. :)

object.AlongWith("this new item");

“WITH”是SETL中的一个中缀运算符,执行相同的操作。如果SETL使用它,那么它一定是正确的 :-) - finnw
1
巴...谁还在用VB呢? :) 哎呀..糟糕..这是不是说出来了?嘿..但是,说真的,这就是为什么我考虑“AlongWith”的原因,它可以解决VB的问题。他可以有无数种选择...我的意思是,甚至包括像object.Plus()或Object.ExistingPlus()这样疯狂的选择...然而,他发布的这个问题确实非常好...嘿.. - LarryF

35
我最终选择在BclExtras中为所有我的不可变集合使用Add。原因是它是一个易于预测的名称。我不太担心人们会将Add与突变添加混淆,因为类型的名称前缀为Immutable。
有一段时间,我考虑过Cons和其他函数式风格的名称。最终我放弃了它们,因为它们没有那么出名。当然,函数式编程者会理解,但他们不是大多数用户。
其他名称:你提到了:
  • Plus:我对此犹豫不决。对我来说,这并不能将其区分为非突变操作
  • With:会在VB中引起问题(双关语)
  • 运算符重载:可发现性将成为问题
我考虑过的选项:
  • Concat:字符串是不可变的,使用它。不幸的是,它只适用于添加到末尾
  • CopyAdd:复制什么?源代码,列表?
  • AddToNewList:也许对于List来说是一个好的选择。但是对于Collection、Stack、Queue等等呢?
不幸的是,似乎没有一个单词能够:
  1. 绝对是一个不可变的操作
  2. 为大多数用户所理解
  3. 用不到4个单词表示

如果考虑除List之外的其他集合,情况会变得更加奇怪。以Stack为例,即使是一年级的程序员也知道Stack有Push/Pop一对方法。如果你创建了一个ImmutableStack并给它一个完全不同的名称,比如叫Foo/Fop,那么使用你的集合就需要更多的工作。

编辑:回应Plus编辑

我明白你的想法。实际上,更强的案例可能是Minus用于删除。如果我看到以下内容,我肯定会想知道程序员在想什么:

list.Minus(obj);

我对加减或新的配对最大的问题是感觉过度了。集合本身已经有一个独特的名称,即不可变前缀。为什么要进一步添加词汇,其意图是添加与不可变前缀已经具有的相同区分?
我可以看到调用站点的论点。从单个表达式的角度来看,它使它更清晰。但在整个函数的上下文中,它似乎是不必要的。
编辑2
同意人们肯定会被String.Concat和DateTime.Add搞混。我见过几个非常聪明的程序员遇到这个问题。
但是我认为ImmutableList是一个不同的论点。没有任何关于String或DateTime将其作为不可变的程序员的标志。您必须通过其他来源简单地知道它是不可变的。因此,混淆是可以预料的。
ImmutableList没有这个问题,因为名称定义了它的行为。您可以争辩说人们不知道Immutable是什么,我认为这也是有效的。我直到大约在大学二年级时才知道它。但是,您选择代替Add的任何名称都会面临相同的问题。
编辑3:像TestSuite这样的类型是不可变的,但不包含该单词?
我认为这强调了您不应该发明新的方法名称的想法。即因为显然有一个推动使类型不可变以便于并行操作。如果您专注于更改集合的方法名称,那么下一步将是在每个不可变类型上更改变异方法名称。
我认为更有价值的努力是将类型标识为不可变。这样,您可以在不重新考虑所有变异方法模式的情况下解决问题。
现在如何将TestSuite标识为不可变?在今天的环境中,我认为有几种方法:
1. 前缀为Immutable:ImmutableTestSuite 2. 添加描述Immutablitiy级别的属性。这显然不太容易发现。 3. 没有其他方法。
我猜测/希望开发工具将开始通过视觉上的简单识别(不同的颜色、更强的字体等)来帮助解决这个问题。但我认为这是更改所有方法名称的答案。

我在问题中添加了喜欢使用“加号”而不是“加法”符号的原因。欢迎对我的推理进行评论。我非常乐意接受自己可能有些愚蠢的想法。 - Jon Skeet
@JaredPar:如果类型叫做TestSuite呢? - Jon Skeet
很想再为第三次编辑点赞,但显然不能这样做。(在我的情况下,我并不试图获得并行性 - 我相信不变性会导致更容易理解的代码。)我仍然不完全相信Add是正确的方法,但这里对它的支持是有说服力的。 - Jon Skeet
如果你能在工作中顺便提起这个问题,那就太棒了。顺便说一下,我已经在一个C#列表上开始了讨论,所以一些同事可能已经参与其中了。我也可能会在Google内部询问... - Jon Skeet
@Jon,我觉得我知道一个好地方可以在工作中提出这个问题 :) - JaredPar
OCaml对其不可变Set库执行相同的操作。在我看来,称其为“add”是可以的。 - nlucaroni

27

我认为这可能是那些可以重载 + 运算符的罕见情况之一。在数学术语中,我们知道 + 并不是将某物附加到其他东西的结尾,而总是将两个值组合在一起并返回一个新的结果值。

例如,很容易理解当你说

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被赋值为将"word"追加到list2的结果。
否则,我只会将函数命名为Plus()。

如果您有一个通用列表,您可以合法地向其中添加一个整数(例如)另一个整数列表,那么使用+将与连接冲突。 - finnw
@finnw:我不明白你的意思。对于列表,无论是添加一个元素还是多个元素,我总是期望“+”表示追加。 - Bill the Lizard
4
对于可重用的API,最好还拥有一个命名方法,以防某些人使用不支持运算符重载的语言调用你的类。 - Neil
"无论你是添加一个元素还是多个元素" -- 如果你想将列表作为一个单一元素添加呢?例如,[1] + 2 = [1,2][1] + [2] = [1,[2]]。你提出的做法行为不一致。这可能是 Python 不允许以这种方式添加一个元素的原因。 - mpen
在像C#这样的静态类型语言中,您可以确定列表元素的类型,无论是列表还是元素列表,因此您可以使用类型来决定是否添加一个列表元素或连接两个列表。 - Dobes Vandermeer

22
为了让意思更加明确,你可能会选择使用较冗长的词语CopyAndAdd或类似的词语。

并非所有不可变集合都需要复制才能添加。想想不可变树,它们只需要一个新节点,就可以将现有的树用作叶子。 - JaredPar
顺便说一下,我看了你的个人资料,按照年龄排序后,短暂地惊讶于你的年龄。但很快就恢复了理智。 - JaredPar
关于第一条评论:我很少使用树形结构,所以从来没有想到过这个问题。这是一个很好的观点。 - Michael Myers
关于第二条评论:我承认,我这么做只是为了获得徽章!我不喜欢透露我的年龄。(实际上我比你还年轻,但如果需要的话,我可以很好地扮演一个脾气暴躁的老年人。而且我的年龄为89岁的并不只有我一个人。) - Michael Myers
不可变树的论点很有力,说得好。那么EquivalentReferenceWithAdded(x)将反驳这个论点,但听起来很愚蠢,而且难以推断。 - TheBlastOne

21

如果你想要非常详细的话,我会称它为Extend()或者ExtendWith()

“Extends”指的是在不改变原有内容的情况下给内容添加其他东西。我认为这是C#中非常相关的术语,因为这类似于扩展方法的概念——它们可以向一个类添加新的方法,而不必修改类本身。

否则,如果你真的想强调完全不修改原始对象,那么使用类似Get-这样的前缀对我来说似乎是不可避免的。


1
仍然有点意味着对基本对象进行某些操作。 - chaos

18

我喜欢Mmyers提出的CopyAndAdd建议。为了保持"变异"主题,也许你可以选择Bud(无性繁殖),GrowReplicateEvolve? =)

编辑:继续沿用我的基因主题,如何考虑Procreate,暗示着基于以前的对象制作了一个新的对象,但添加了一些新的东西。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接