不变的“添加"名称的最佳名称是什么?不可变集合上的方法? [英] What's the best name for a non-mutating "add" method on an immutable collection?

查看:65
本文介绍了不变的“添加"名称的最佳名称是什么?不可变集合上的方法?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

对不起,标题太小了-如果我能拿出一个简洁的标题,我就不必问这个问题了.

假设我有一个不变的列表类型.它具有操作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:描述性不强.
  • +的运算符重载:我真的不太喜欢;我通常认为运算符应仅应用于较低级别的类型.我还是愿意被说服!

我要选择的标准是:

  • 给出对方法调用结果的正确印象(即它是带有额外元素的原始列表)
  • 尽可能清楚地表明它不会使现有列表发生变异
  • 如上面的第二个示例中所述,当链接在一起时听起来很合理

如果我不够清楚的话,请询问更多详细信息...

这是我偏爱Plus而不是Add的原因.考虑以下两行代码:

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

在我看来(这是个人的东西),后者显然是越野车-就像写"x + 5;"作为自己的陈述.第一行看起来还可以,直到您记住它是不可变的.实际上,加号运算符本身不会改变其操作数的方式是Plus是我最喜欢的另一个原因.在没有轻微的运算符重载的情况下,它仍然具有相同的含义,包括(对我而言)不对操作数(在这种情况下为方法目标)进行变异.

不喜欢添加的原因.

各种答案实际上是有效的:使用Add.DateTime就是这样做的,而String具有Replace的方法等等,这些都不能使不变性变得显而易见."我同意-这里有优先权.但是,我已经看到很多人将其称为DateTime.AddString.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>一样的自描述性:)

解决方案

也许混淆源于您希望一次执行两个操作的事实.为什么不将它们分开? 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);
}

现在,用户不再有可能会曲解,他们将自然而然地陷入成功之路.

又一次更新:

如果您仍然不喜欢可变列表,即使现在已经包含了可变列表,您也可以设计一个规范对象,该规范对象将对 specify script 复制操作将转换其列表.用法将是相同的:

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

Sorry for the waffly title - if I could come up with a concise title, I wouldn't have to ask the question.

Suppose I have an immutable list type. It has an operation Foo(x) which returns a new immutable list with the specified argument as an extra element at the end. So to build up a list of strings with values "Hello", "immutable", "world" you could write:

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

(This is C# code, and I'm most interested in a C# suggestion if you feel the language is important. It's not fundamentally a language question, but the idioms of the language may be important.)

The important thing is that the existing lists are not altered by Foo - so empty.Count would still return 0.

Another (more idiomatic) way of getting to the end result would be:

var list = new ImmutableList<string>().Foo("Hello")
                                      .Foo("immutable")
                                      .Foo("word");

My question is: what's the best name for Foo?

EDIT 3: As I reveal later on, the name of the type might not actually be ImmutableList<T>, which makes the position clear. Imagine instead that it's TestSuite and that it's immutable because the whole of the framework it's a part of is immutable...

(End of edit 3)

Options I've come up with so far:

  • Add: common in .NET, but implies mutation of the original list
  • Cons: I believe this is the normal name in functional languages, but meaningless to those without experience in such languages
  • Plus: my favourite so far, it doesn't imply mutation to me. Apparently this is also used in Haskell but with slightly different expectations (a Haskell programmer might expect it to add two lists together rather than adding a single value to the other list).
  • With: consistent with some other immutable conventions, but doesn't have quite the same "additionness" to it IMO.
  • And: not very descriptive.
  • Operator overload for + : I really don't like this much; I generally think operators should only be applied to lower level types. I'm willing to be persuaded though!

The criteria I'm using for choosing are:

  • Gives the correct impression of the result of the method call (i.e. that it's the original list with an extra element)
  • Makes it as clear as possible that it doesn't mutate the existing list
  • Sounds reasonable when chained together as in the second example above

Please ask for more details if I'm not making myself clear enough...

EDIT 1: Here's my reasoning for preferring Plus to Add. Consider these two lines of code:

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

In my view (and this is a personal thing) the latter is clearly buggy - it's like writing "x + 5;" as a statement on its own. The first line looks like it's okay, until you remember that it's immutable. In fact, the way that the plus operator on its own doesn't mutate its operands is another reason why Plus is my favourite. Without the slight ickiness of operator overloading, it still gives the same connotations, which include (for me) not mutating the operands (or method target in this case).

EDIT 2: Reasons for not liking Add.

Various answers are effectively: "Go with Add. That's what DateTime does, and String has Replace methods etc which don't make the immutability obvious." I agree - there's precedence here. However, I've seen plenty of people call DateTime.Add or String.Replace and expect mutation. There are loads of newsgroup questions (and probably SO ones if I dig around) which are answered by "You're ignoring the return value of String.Replace; strings are immutable, a new string gets returned."

Now, I should reveal a subtlety to the question - the type might not actually be an immutable list, but a different immutable type. In particular, I'm working on a benchmarking framework where you add tests to a suite, and that creates a new suite. It might be obvious that:

var list = new ImmutableList<string>();
list.Add("foo");

isn't going to accomplish anything, but it becomes a lot murkier when you change it to:

var suite = new TestSuite<string, int>();
suite.Add(x => x.Length);

That looks like it should be okay. Whereas this, to me, makes the mistake clearer:

var suite = new TestSuite<string, int>();
suite.Plus(x => x.Length);

That's just begging to be:

var suite = new TestSuite<string, int>().Plus(x => x.Length);

Ideally, I would like my users not to have to be told that the test suite is immutable. I want them to fall into the pit of success. This may not be possible, but I'd like to try.

I apologise for over-simplifying the original question by talking only about an immutable list type. Not all collections are quite as self-descriptive as ImmutableList<T> :)

解决方案

Maybe the confusion stems from the fact that you want two operations in one. Why not separate them? DSL style:

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

Copy would return an intermediate object, that's a mutable copy of the original list. With would return a new immutable list.

Update:

But, having an intermediate, mutable collection around is not a good approach. The intermediate object should be contained in the Copy operation:

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

Now, the Copy operation takes a delegate, which receives a mutable list, so that it can control the copy outcome. It can do much more than appending an element, like removing elements or sorting the list. It can also be used in the ImmutableList constructor to assemble the initial list without intermediary immutable lists.

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

Now there's no possibility of misinterpretation by the users, they will naturally fall into the pit of success.

Yet another update:

If you still don't like the mutable list mention, even now that it's contained, you can design a specification object, that will specify, or script, how the copy operation will transform its list. The usage will be the same:

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

Now you can be creative with the rules names and you can only expose the functionality that you want Copy to support, not the entire capabilities of an IList.

For the chaining usage, you can create a reasonable constructor (which will not use chaining, of course):

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

...

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

Or use the same delegate in another constructor:

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

This assumes that the rules.Append method returns this.

This is what it would look like with your latest example:

var suite = new TestSuite<string, int>(x => x.Length);
var otherSuite = suite.Copy(rules => 
  rules
    .Append(x => Int32.Parse(x))
    .Append(x => x.GetHashCode())
);

这篇关于不变的“添加"名称的最佳名称是什么?不可变集合上的方法?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆