我最近问了一个关于函数式编程的问题,并收到了(好的!)答案,这些答案提出了更多问题(有时似乎是学习的情况).以下是几个例子:
一个答案提到了不可变数据结构的优点:每个线程都可以拥有自己的副本.现在,对我来说,这听起来更像是一个版本控制系统(使用类比),而不是锁定某人已经签出的代码,以便其他人无法修改,每个人都可以查看自己的副本.听起来不错.但是,在VCS中,您有"合并"更改的概念,如果两个人更改了相同的内容.似乎这个问题肯定会出现在多线程场景中......那么当线程看到最新数据时,如何完成"合并"呢?
这个答案讨论了在对象的循环中执行操作的情况,以及如何每次使用新对象而不是更新旧对象.但是,假设bankAccount
正在非循环场景中进行更新 - 例如GUI银行系统.操作员单击"更改利率"按钮,该按钮将触发一个事件(例如,在C#中)执行类似操作bankAccount.InterestRate = newRateFromUser
.我觉得我在这里很密集,但希望我的例子是有意义的:必须有某种方式来更新对象,对吧?其他一些事情可能取决于新数据.
无论如何,如果你能帮助我了解范式转变,我会很感激.我记得我的大脑经过类似的"愚蠢阶段",在学习OOP后,采用简单的程序性命令式编码方法.
想想.Net中的String类(它是一个不可变对象).如果在字符串上调用方法,则会获得一个新副本:
String s1 = "there"; String s2 = s1.Insert(0, "hello "); Console.Writeline("string 1: " + s1); Console.Writeline("string 2: " + s2);
这将输出:
字符串1:那里
字符串2:你好
将此行为与StringBuilder进行比较,StringBuilder具有基本相同的方法签名:
StringBuilder sb = new StringBuilder("there"); StringBuilder sb2 = sb.Insert(0, "hi "); Console.WriteLine("sb 1: " + sb.ToString()); Console.WriteLine("sb 2: " + sb2.ToString());
因为StringBuilder是可变的,所以两个变量都指向同一个对象.输出将是:
某人1:你好
某人2:你好
因此,一旦创建了字符串,就绝对无法更改字符串.s1将一直"存在"直到时间结束(或直到它的垃圾收集).这在线程中很重要,因为你总是可以逐步浏览每个角色并打印它的值,因为它知道它将始终打印在那里.如果你在创建后开始打印StringBuilder,你可以打印那里的前两个字符并得到''.现在,想象另一个线程出现广告插入'hi'.价值现在不同了!当你打印第三个字符时,它是'hi'的空格.所以你打印:'那里'.
对第1部分的回答:不可变对象本身不支持"合并"之类的任何东西,以允许组合两个线程更新的结果.有两个主要策略:悲观和乐观.如果你是悲观的,你会认为两个线程很可能想要同时更新同一条数据.因此,您使用锁定,这样第二个线程将冻结,直到第一个线程说它已完成.如果您乐观地认为这种情况很少发生,那么您可以让两个线程都使用自己的数据逻辑副本.完成的那个首先提供新版本,另一个必须从头开始 - 只是现在它从第一个线程的更改结果开始.这种昂贵的重启仅偶尔发生,
第2部分:纯功能无状态语言并没有真正消除这个问题.即使是纯粹的Haskell程序也可以拥有与之相关的状态.不同之处在于有状态代码具有不同的返回类型.操纵状态的函数表示为对表示该状态的对象进行操作的一系列操作.在一个荒谬的例子中,考虑计算机的文件系统.每次程序修改文件的内容(即使是单个字节),它都会创建整个文件系统的新"版本".并且通过扩展,整个宇宙的新版本.但是现在让我们关注文件系统.检查文件系统的程序的任何其他部分现在可能受该修改字节的影响.因此,Haskell说在文件系统上运行的函数必须有效地传递代表文件系统版本的对象.然后因为手动处理这将是繁琐的,它将需求内部化,并说如果一个函数想要能够做IO,它必须返回一种容器对象.容器内部是函数想要返回的值.但该容器可作为该功能也具有副作用或可见副作用的证据.这意味着Haskell的类型系统能够区分功能与副作用和"纯"功能.因此,有助于包含和管理代码的有状态,而无需真正消除代码.它将需求转化为内部,并表示如果函数想要能够执行IO,则必须返回一种容器对象.容器内部是函数想要返回的值.但该容器可作为该功能也具有副作用或可见副作用的证据.这意味着Haskell的类型系统能够区分功能与副作用和"纯"功能.因此,有助于包含和管理代码的有状态,而无需真正消除代码.它将需求转化为内部,并表示如果函数想要能够执行IO,则必须返回一种容器对象.容器内部是函数想要返回的值.但该容器可作为该功能也具有副作用或可见副作用的证据.这意味着Haskell的类型系统能够区分功能与副作用和"纯"功能.因此,有助于包含和管理代码的有状态,而无需真正消除代码.s型系统能够区分功能与副作用和"纯"功能.因此,有助于包含和管理代码的有状态,而无需真正消除代码.s型系统能够区分功能与副作用和"纯"功能.因此,有助于包含和管理代码的有状态,而无需真正消除代码.