字符串类型不可变的非技术优势

2022-09-03 01:26:37

我想知道从程序员的角度来看,让字符串类型不可变的好处。

技术优势(在编译器/语言方面)可以总结为,如果类型不可变,则更容易进行优化。有关相关问题,请阅读此处

此外,在可变字符串类型中,要么您已经内置了线程安全(然后,优化更难做到),要么您必须自己动手。在任何情况下,您都可以选择使用具有内置线程安全性的可变字符串类型,因此这并不是不可变字符串类型的真正优势。(同样,进行处理和优化以确保不可变类型的线程安全性会更容易,但这不是重点。

但是,在用法中使用不可变字符串类型有什么好处呢?让某些类型不可变而其他类型不可变有什么意义?这在我看来是非常不一致的。

在C++中,如果我想让一些字符串不可变,我会将其作为对函数()的const引用传递。如果我想拥有原始字符串的可更改副本,我会将其作为.只有当我想让它可变时,我才会将其作为参考传递()。所以我只能选择我想做什么。我可以用每种可能的类型来做到这一点。const std::string&std::stringstd::string&

在Python或Java中,某些类型是不可变的(主要是所有基元类型和字符串),其他类型则不是。

在像Haskell这样的纯函数式语言中,一切都是不可变的。

有没有理由让这种不一致变得有意义?还是纯粹出于技术低级原因?


答案 1

让某些类型不可变而其他类型不可变有什么意义?

如果没有一些可变类型,你将不得不全力以赴进行纯函数式编程 - 一种与目前最流行的OOP和过程方法完全不同的范式,虽然非常强大,但显然对许多程序员来说非常具有挑战性(当你确实需要一种没有任何可变的语言中的副作用时会发生什么, 当然,在现实世界的编程中,你不可避免地会这样做,这是挑战的一部分 - 例如,Haskell的Monads是一种非常优雅的方法,但是你知道有多少程序员完全和自信地理解它们,并且可以使用它们以及典型的OOP结构?-)。

如果你不了解拥有多种范式的巨大价值(FP一那些关键依赖于可变数据的范式),我建议你研究Haridi和Van Roy的杰作,计算机编程的概念,技术和模型 - “21世纪的SICP”,正如我曾经描述的那样;-)。

大多数程序员,无论是否熟悉Haridi和Van Roy,都会很容易地承认,至少拥有一些可变的数据类型对他们来说很重要。尽管我上面引用了你的Q中的句子,它采取了完全不同的观点,但我相信这也可能是你困惑的根源:不是“为什么每个”,而是“为什么一些不可变的”。

“完全可变”的方法曾经(意外地)在Fortran实现中获得。如果你有,比如说,

  SUBROUTINE ZAP(I)
  I = 0
  RETURN

然后程序片段执行,例如,

  PRINT 23
  ZAP(23)
  PRINT 23

将打印 23,然后打印 0 - 数字 23 已发生突变,因此程序其余部分对 23 的所有引用实际上都是指 0。从技术上讲,这不是编译器中的错误:Fortran在将常量与变量传递给分配给其参数的过程时,对你的程序有微妙的规则,并且不允许这样做,并且这个片段违反了那些鲜为人知的,非编译器可执行的规则,所以它是在程序中,而不是在编译器中。当然,在实践中,以这种方式引起的错误数量高得令人无法接受,因此在这种情况下,典型的编译器很快就切换到破坏性较小的行为(如果操作系统支持,将常量放在只读段中以获得运行时错误;或者,传递常量的新副本而不是常量本身,尽管有开销;等等),即使从技术上讲,它们是程序错误,允许编译器显示未定义的行为。正确“;-)。

在其他一些语言中强制实施的替代方法是添加多种参数传递方式的复杂性 - 最值得注意的是,也许在C++中,按值,按引用,按常量引用,按指针,通过常量指针,...然后,当然,你会看到程序员对诸如这样的声明感到困惑(如果它是某些函数的参数,则最右边基本上是无关紧要的......但关键是,如果是局部变量...!-).const foo* const barconstbarbar

实际上,Algol-68可能在这个方向上走得更远(如果你能有一个值和一个参考,为什么不引用一个参考?或者引用引用引用?&c - Algol 68对此没有限制,定义正在发生的事情的规则可能是“用于实际使用”编程语言中发现的最微妙,最难的组合)。早期的C(只有按值和按显式指针 - 没有,没有引用,没有复杂性)无疑是对它的反应,就像最初的Pascal一样。但很快就悄悄溜进来,并发症又开始增加。constconst

Java和Python(以及其他语言)用一把强大的简单砍刀穿过了这个灌木丛:所有参数传递所有赋值都是“通过对象引用”(从不引用变量或其他引用,从不在语义上隐式副本,&c)。将数字定义为(至少)语义上不可变的,通过避免诸如上面的Fortran代码所展示的“哎呀”来保持程序员的理智(以及语言简单性的这一宝贵方面)。

将字符串视为原始值就像数字一样与语言预期的高语义级别非常一致,因为在现实生活中,我们确实需要与数字一样简单易用的字符串;将字符串定义为字符列表(Haskell)或字符数组(C)等替代方法对编译器(在此类语义下保持高效性能)和程序员(有效地忽略这种任意结构以允许将字符串用作简单的基元,就像现实生活中的编程经常需要的那样)提出了挑战。

Python通过添加一个简单的不可变容器()并将哈希与“有效的不可变性”联系起来(这避免了程序员的某些惊喜,例如,在Perl中,其哈希允许可变字符串作为键) - 为什么不呢?一旦你有了不变性(一个宝贵的概念,使程序员不必学习N个不同的语义来进行赋值和参数传递,N往往会随着时间的推移而增加;-),你也可以从中获得全部里程;-)。tuple


答案 2

我不确定这是否符合非技术条件:如果字符串是可变的,那么大多数(*)集合需要制作其字符串密钥的私有副本。

否则,外部更改为“bar”的“foo”键将导致“bar”位于集合的内部结构中,其中“foo”是预期的。这样,“foo”查找将找到“bar”,这不是一个问题(不返回任何内容,重新索引有问题的键),但“bar”查找将找不到任何内容,这是一个更大的问题。

(*)在每次查找时对所有键进行线性扫描的哑集合不必这样做,因为它自然会适应键更改。