抱歉标题过于冗长,如果我能想出一个简洁的标题,我就不用问这个问题了。
假设我有一个不可变列表类型。它有一个操作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
所做的,还有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>
那样自描述 :)