首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >专业人士。/ Cons。不变性与易变性

专业人士。/ Cons。不变性与易变性
EN

Stack Overflow用户
提问于 2009-12-07 22:57:18
回答 5查看 15.5K关注 0票数 27

嗯,我想这是明确的方向,我试图从这方面进行推理。这些天有很多关于不变(不变)的优点的讨论,只要有可能。Java书中的并发编程也谈到了这一点。

然而,这一切正是我所读到的。就我个人而言,我没有在函数式语言中进行过多的编码。在我看来,它可以舒适地与不变的物体一起工作,这让我感到非常惊讶。从理论上讲,这是绝对可能的。但是,从实践的角度来看,是一种非常舒适的体验。或者,什么是新的推理(对于FP),我必须发展,以使我不需要如此多的可变。

当您被迫使用不可变的对象时,我想知道如何编写程序。

  • 已经把这里变成了社区维基..。对于那些有兴趣结束这个问题或者把这个问题标记为主观的人来说.等等.*
EN

回答 5

Stack Overflow用户

回答已采纳

发布于 2009-12-07 23:48:00

许多功能语言是非纯的(允许变异和副作用)。

例如,f#是这样的,如果您查看集合中的一些非常低级别的构造,您会发现,有几个在幕后使用迭代,并且有相当多的使用一些可变状态(如果您想获取序列的前n个元素,那么拥有一个计数器就容易得多)。

诀窍是,这通常是为了:

  1. 少用
  2. 当你做时,请注意
    • 请注意,在f#中,您必须声明某些内容是可变的。

大量的功能代码证明了在很大程度上避免发生变异状态的可能性。对于使用命令式语言的人来说,这有点难以理解,特别是以前用循环作为递归函数编写代码。更棘手的是,在可能的情况下,将它们编写为尾递归。了解如何做到这一点是有益的,并可能导致更多的表达解决方案,侧重于逻辑,而不是实现。好的例子是那些处理集合的例子,在这些集合中,no、一个或多个元素的“基本情况”被清晰地表达出来,而不是循环逻辑的一部分。

虽然事情更好,但事实确实如此。最好的方法是通过一个例子:

使用您的代码库并将每个实例变量更改为readonly1。只更改那些您需要它们是可变的代码才能运行(如果您只在构造函数之外设置它们一次,可以考虑通过属性之类的东西使它们成为构造函数的参数,而不是可变的参数)。

有些代码库不能很好地工作,例如gui/小部件重代码和一些库(特别是可变的集合),但我要说的是,大多数合理的代码将允许50%以上的实例字段是只读的。

此时,您必须扪心自问:“为什么默认情况是可变的?”可变字段实际上是程序的一个复杂方面,因为它们的交互,即使是在一个线程世界中,也有更大的空间来处理不同的行为;因此,它们最好被突出显示,并提请编码器注意,而不是让世界的蹂躏“赤裸裸”。

值得注意的是,大多数函数式语言要么没有null的概念,要么很难使用,因为它们使用的不是变量,而是同时定义其值(好范围)的命名值。

  1. 不幸的是,c#也没有通过局部变量复制java的不可变概念。能够强调某些东西不会改变,这有助于明确一个值是在堆栈上还是在一个对象/结构中。
  2. 如果您有NDepend,那么您可以在WARN IF Count > 0 IN SELECT FIELDS WHERE IsImmutable AND !IsInitOnly中找到这些
票数 4
EN

Stack Overflow用户

发布于 2009-12-08 00:59:06

但是,从实践的角度来看,是一种非常舒适的体验。

我喜欢在大部分函数式编程中使用F#。值得注意的是,您可以用C#编写函数式代码,但读起来真的很讨厌,很难看。此外,我发现GUI开发抵制函数式编程风格。

幸运的是,业务代码似乎很好地适应了功能风格:)以及web开发--考虑一下,每个HTTP请求都是无状态的。每次您“修改”状态时,都会传递给服务器某种状态,然后服务器返回一个全新的页面。

当您被迫使用不可变的对象时,我想知道如何编写程序。

不可变对象应该是小

在大多数情况下,当对象的内部属性少于3或4个时,我发现不可变的数据结构最容易处理。例如,红黑树中的每个节点都有4个属性:颜色、值、左子节点和右子节点。堆栈有两个属性,一个值和一个指向下一个堆栈节点的指针。

以您公司的数据库为例,您可能拥有具有20、30、50个属性的表。如果您需要在整个应用程序中修改这些对象,那么我肯定会抵制使这些对象不可变的冲动。

C# /C#/ C++不是很好的函数语言。使用Haskell、OCaml或F#代替

根据我自己的经验,与C语言相比,用类似ML的语言读写不可变的对象要容易1000倍。对不起,但是一旦您有了模式匹配和联合类型,就不能放弃它们:)此外,一些数据结构可以利用尾调用优化,这是一些类似C语言所没有的特性。

不过,为了好玩,下面是C#中的一个不平衡二叉树:

代码语言:javascript
复制
class Tree<T> where T : IComparable<T>
{
    public static readonly ITree Empty = new Nil();

    public interface ITree
    {
        ITree Insert(T value);
        bool Exists(T value);
        T Value { get; }
        ITree Left { get; }
        ITree Right { get; }
    }

    public sealed class Node : ITree
    {
        public Node(T value, ITree left, ITree right)
        {
            this.Value = value;
            this.Left = left;
            this.Right = right;
        }

        public ITree Insert(T value)
        {
            switch(value.CompareTo(this.Value))
            {
                case 0 : return this;
                case -1: return new Node(this.Value, this.Left.Insert(value), this.Right);
                case 1: return new Node(this.Value, this.Left, this.Right.Insert(value));
                default: throw new Exception("Invalid comparison");
            }
        }

        public bool Exists(T value)
        {
            switch (value.CompareTo(this.Value))
            {
                case 0: return true;
                case -1: return this.Left.Exists(value);
                case 1: return this.Right.Exists(value);
                default: throw new Exception("Invalid comparison");
            }
        }

        public T Value { get; private set; }
        public ITree Left { get; private set; }
        public ITree Right { get; private set; }
    }

    public sealed class Nil : ITree
    {
        public ITree Insert(T value)
        {
            return new Node(value, new Nil(), new Nil());
        }

        public bool Exists(T value) { return false; }

        public T Value { get { throw new Exception("Empty tree"); } }
        public ITree Left { get { throw new Exception("Empty tree"); } }
        public ITree Right { get { throw new Exception("Empty tree"); } }
    }
}

Nil类表示一个空树。与空表示相比,我更喜欢这种表示形式,因为空检查是魔鬼的化身:)

每当我们添加一个节点,我们就会创建一个全新的树,并插入节点。这比听起来更有效,因为我们不需要复制树中的所有节点;我们只需要“在下行过程中”复制节点,并重用任何没有更改的节点。

假设我们有一棵这样的树:

代码语言:javascript
复制
       e
     /   \
    c     s
   / \   / \
  a   b f   y

好的,现在我们要把w插入到列表中。我们将从根e开始,移动到s,然后移动到y,然后用w替换y的左子节点。我们需要创建节点的副本:

代码语言:javascript
复制
       e                     e[1]
     /   \                 /   \
    c     s      --->     c     s[1]
   / \   / \             / \    /\  
  a   b f   y           a   b  f  y[1]
                                  /
                                 w

好了,现在我们插入一个g

代码语言:javascript
复制
       e                     e[1]                   e[2]
     /   \                 /   \                   /    \
    c     s      --->     c     s[1]      -->     c      s[2]
   / \   / \             / \    /\               / \     /   \
  a   b f   y           a   b  f  y[1]          a   b  f[1]   y[1]
                                  /                      \    /
                                 w                        g  w

我们重用树中的所有旧节点,因此没有理由从头开始重建整个树。这棵树的计算复杂度与其可变的对应树相同。

它还相当容易编写不变版本的红黑树,AVL树,基于树的堆,以及许多其他的数据结构。

票数 9
EN

Stack Overflow用户

发布于 2009-12-07 23:14:00

例如,不可变性在多线程程序中有好处。由于不可变对象不能在构造后更改它们的状态,因此可以在任意数量的并发运行线程之间安全地共享它们,而不会发生一次干扰(通过更改其他线程可见的对象的状态)与另一线程之间的干扰。

另一个优点是,用函数样式编写的程序的语义更容易推理(因此,没有副作用)。函数式编程在本质上更具有声明性,强调结果应该是什么,结果是怎样实现的。不可变的数据结构可以帮助使您的程序在样式上更有功能。

关于这个话题,马克·朱-卡罗尔有一个不错的博客条目

票数 6
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/1863515

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档