代码之家  ›  专栏  ›  技术社区  ›  Albert

字符串类型不可变的非技术好处

  •  17
  • Albert  · 技术社区  · 15 年前

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

    技术上的好处(在编译器/语言方面)可以概括为,如果类型是不可变的,则更容易进行优化。读 here 有关问题。

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

    但是,使用不可变字符串类型有什么好处呢?有一些类型是不可变的,而另一些类型是不可变的,这有什么意义呢?我觉得这很矛盾。

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

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

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

    有没有充分的理由说明这种不一致性是合理的?或者仅仅是出于技术上的低级原因?

    8 回复  |  直到 15 年前
        1
  •  16
  •   Alex Martelli    15 年前

    吃点什么呢 类型不可变,其他类型不可变?

    没有 一些 可变类型,你必须全力以赴地进行纯粹的函数式编程,这是一种完全不同于OOP和过程式编程的模式,后者目前最受欢迎,而且,虽然非常强大,但对很多程序员来说显然非常具有挑战性(当你 在没有什么是可变的语言中需要副作用,当然在现实的编程中不可避免地需要副作用,这是挑战的一部分——haskell的 Monads 例如,这是一种非常优雅的方法,但是您知道有多少程序员完全并且自信地理解它们,并且能够使用它们以及典型的OOP构造?-)

    如果你不理解拥有多个范例的巨大价值(两个都是FP-One 最关键的是依赖可变数据),我建议学习哈里迪和范罗伊的杰作, Concepts, Techniques, and Models of Computer Programming ——“A” SICP “对于21世纪”,正如我曾经描述的那样;-)。

    大多数程序员,无论是否熟悉Haridi和van Roy,都会欣然承认至少 一些 可变数据类型对它们很重要。尽管我从你的问题中引用了一个完全不同的观点的句子,但我相信这也可能是你困惑的根源: “为什么每种都有”,而是“为什么有的” 不变的 “完全”。

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

      SUBROUTINE ZAP(I)
      I = 0
      RETURN
    

    然后是一个程序片段,例如,

      PRINT 23
      ZAP(23)
      PRINT 23
    

    将打印23,然后打印0 23号 已经发生了变异,所以在程序的其余部分中对23的所有引用实际上都指向0。从技术上讲,这不是编译器中的一个错误:Fortran对程序是什么有着微妙的规则,并且不允许在将常量和变量传递给分配给其参数的过程中执行这些操作,而且这个代码段违反了那些鲜为人知的、非编译器可执行的规则,所以它只是程序中的一个,而不是编译器中的一个。当然,在实践中,这种方式导致的错误数量是不可接受的,因此在这种情况下,典型的编译器很快就转向破坏性较低的行为(如果操作系统支持,将常量放入只读段以获取运行时错误;或者,传递新的 复制 而不是常量本身,尽管有开销;等等),即使从技术上来说,它们是程序错误,允许编译器“正确”地显示未定义的行为;-)。

    在其他语言中执行的另一种选择是增加参数传递的多种方式的复杂性——最明显的可能是C++,用值、引用、常数引用、指针、常量指针、…当然,您会看到程序员被诸如 const foo* const bar (最右边的 const 如果 bar 是某个函数的参数…但如果 酒吧 是一个 局部变量 ……!-)

    实际上,algol-68可能沿着这个方向走得更远(如果您可以有一个值和一个引用,为什么不引用一个引用呢?还是参考参考?&C--algol 68对此没有任何限制,定义正在发生的事情的规则可能是“真正使用的”编程语言中发现的最微妙、最难的混合。早期的C(只通过值和显式指针——不 康斯特 毫无疑问,这在一定程度上是对它的反应,就像最初的帕斯卡一样。但是 康斯特 很快,并发症又开始增多。

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

    把字符串看作原语就像数字一样,与语言的高语义水平非常一致,因为在现实生活中,我们 需要与数字一样简单的字符串;将字符串定义为字符列表(haskell)或字符数组(c)等替代方法对编译器(在这种语义下保持高效的性能)和程序员(有效地忽略这种任意结构,以使字符串能够作为simp使用)都构成了挑战。le原语,正如现实编程经常需要的那样)。

    通过添加一个简单的不可变容器,python做得更进一步。( tuple 打捆 哈希 为了“有效的不可变性”(这可以避免程序员在Perl中发现某些意外,例如,它的散列允许可变字符串作为键),为什么不呢?一旦你有了不变性(这是一个宝贵的概念,它可以避免程序员必须学习n种不同的赋值和参数传递语义,n随着时间的推移而增加;-),你就可以充分利用它;-。

        2
  •  2
  •   Rafał Dowgird    15 年前

    不过,我不确定这是否符合非技术条件:如果字符串是可变的,那么大多数(*)集合都需要对其字符串键进行私有复制。

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

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

        3
  •  1
  •   Katriel    15 年前

    没有 总体的、基本的 不具有可变字符串的原因。对于它们的不可变性,我发现最好的解释是它促进了一种更为实用、副作用更小的编程方式。最后变得更干净,更优雅,更像是蟒蛇。

    从语义上讲,它们应该是不变的,不是吗?弦 "hello" 应始终代表 “你好” . 你不能改变它超过你可以改变数字三!

        4
  •  1
  •   Benjamin Wootton    15 年前

    不确定您是否会将其视为“技术低级别”的好处,但不可变字符串隐式线程安全这一事实为您节省了大量线程安全编码工作。

    有点玩具的例子…

    线程A-检查登录名为foo的用户有权执行某些操作,返回true

    线程B-将用户字符串修改为登录名栏

    线程A-由于以前对foo的权限检查,使用登录名栏执行一些操作。

    字符串不能更改的事实为您节省了防范这种情况的努力。

        5
  •  1
  •   Jochen Ritzel    15 年前

    如果你想要完全一致,你只能 一切不变 因为可变的bools或ints根本没有意义。事实上,有些函数语言就是这样做的。

    python的哲学是“简单胜于复杂”。在C语言中,您需要意识到字符串会发生变化,并思考这会如何影响您。python假设字符串的默认用例是“将文本放在一起”——您完全不需要了解字符串来完成这项工作。但是如果你 希望 要更改字符串,只需使用更合适的类型(即列表、字符串IO、模板等)。

        6
  •  1
  •   fredoverflow    15 年前

    在具有用户定义类型的引用语义的语言中,具有可变字符串将是一个desaster,因为每次分配一个字符串变量时,都要对可变字符串对象进行别名,并且必须在各处进行防御性复制。这就是为什么字符串在爪哇和C中是不可变的——如果字符串对象是不可变的,那么它指向多少变量并不重要。

    注意,在C++中,两个字符串变量从不共享状态(至少在概念上)——技术上,可能存在。 写上拷贝 继续,但由于多线程场景中的效率低下,这已经过时了)。

        7
  •  1
  •   supercat    15 年前

    如果字符串是可变的,那么字符串的许多使用者将不得不对其进行复制。如果字符串是不可变的,那么这就不那么重要了(除非不可变是由硬件互锁强制实现的,否则对于一些具有安全意识的字符串使用者来说,如果给定的字符串没有它们应该的那样不可变,那么制作它们自己的副本可能不是一个坏主意)。

    StringBuilder类非常好,不过我认为如果它有一个“value”属性(read相当于toString,但它会出现在对象检查器中;write允许直接设置整个内容)和默认的扩展转换为字符串的话会更好。从理论上讲,将可变字符串类型派生自具有字符串的公共祖先是很好的,因此可以将可变字符串传递给一个不关心字符串是否可变的函数,尽管我怀疑依赖于字符串具有特定固定实现这一事实的优化可能不太有效。

        8
  •  1
  •   Wouter Lievens    15 年前

    程序员的主要优点是,使用可变字符串时,您不必担心谁会更改您的字符串。因此,您不必有意识地决定“我应该在这里复制这个字符串吗?”.