很抱歉标题有点啰嗦——如果我能想出一个简洁的标题,我就不必问这个问题了。
假设我有一个不可变的列表类型。它有一个操作,该操作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);
在我看来(这是个人的事情),后者显然是有问题的——就像写“x + 5;”一样。作为自己的声明。第一行看起来没问题,直到您记住它是不可变的。Plus
事实上,加号运算符本身不会改变其操作数的方式是我最喜欢的另一个原因。如果没有运算符重载的轻微恶心,它仍然给出相同的含义,其中包括(对我而言)不改变操作数(或在这种情况下的方法目标)。
编辑2:不喜欢添加的原因。
各种答案都是有效的:“使用 Add。这就是DateTime
它的作用,并且String
具有Replace
不会使不变性明显的方法等。” 我同意 - 这里有优先级。但是,我看到很多人打电话DateTime.Add
orString.Replace
并期待 mutation。有很多新闻组问题(如果我仔细研究的话,可能还有一些问题),这些问题的答案是“你忽略了 ; 的返回值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>
并非所有收藏都像:)那样具有自我描述性