毫无疑问,我被语言,其性质,领域,甚至我们使用该语言的方式偏向于在C ++中应用此类概念。但是考虑到这些因素,我认为设计是一成不变的是最有趣的方面,当涉及到收获批量的函数式编程带来的好处,如线程安全,便于对系统的推理,寻找更多的重用的功能(并发现我们可以以任何顺序组合它们,而不会带来令人不愉快的惊喜),等等。
以这个简单的C ++示例为例(为避免简单起见,没有进行优化,以避免使自己在任何图像处理专家面前都感到尴尬):
// Inputs an image and outputs a new one with the specified size.
Image resized_image(const Image& src, int new_w, int new_h)
{
Image dst(new_w, new_h);
for (int y=0; y < new_h; ++y)
{
for (int x=0; x < new_w; ++x)
dst[y][x] = src.sample(x / (float)new_w, y / (float)new_h);
}
return dst;
}
尽管该函数的实现以两个计数器变量和一个临时本地图像的形式更改了本地(和临时)状态,但它没有外部副作用。它输入图像并输出新图像。我们可以将其多线程化到我们内心的内容。这很容易推理,易于彻底测试。这是异常安全的,因为如果抛出任何异常,新图像将被自动丢弃,并且我们不必担心会回退外部副作用(可以说,没有外部图像在函数范围之外被修改)。
通过Image
在C ++中在上述上下文中实现不可变,我看不到有什么收获,也有可能失去的很多,除了可能使上述函数难以实现,而且效率可能更低。
纯度
如此纯净的功能(无需外部副作用)对我来说非常有趣,并且我强调了即使在C ++中也经常使它们偏向团队成员的重要性。但是不可变的设计,通常只在没有上下文和细微差别的情况下应用,对我来说几乎没有那么有趣,因为鉴于语言的命令性,在高效的过程中能够对某些局部临时对象进行有效的变异通常是有用且实用的(两者都有)。 (对于开发人员和硬件而言)实现纯功能。
廉价复制大型结构
我发现的第二个最有用的属性是能够廉价地复制真正繁重的数据结构,而这样做的代价是不平凡的,因为这样做通常会使函数纯净(鉴于严格的输入/输出性质)。这些不是可以放在堆栈上的小结构。它们将是大型的重型结构,就像整个Scene
视频游戏。
在那种情况下,复制开销可能会阻止有效并行的机会,因为如果物理使渲染器同时尝试绘制的场景发生变异,同时使物理深度变深,则可能很难在不相互锁定和瓶颈的情况下有效地并行化物理和渲染。复制整个游戏场景,仅输出一帧并应用物理效果可能同样无效。但是,如果物理系统是“纯粹的”,即仅输入一个场景并输出一个应用了物理的新场景,并且这种纯度不以牺牲天文复制开销为代价,那么它可以安全地与渲染器,无需一个等待另一个。
因此,能够廉价地复制应用程序状态的真正大量数据并以最小的处理和内存使用成本输出新的,经过修改的版本的能力,确实可以为纯净和有效的并行性打开新的大门,在这里,我发现了很多教训从如何实现持久性数据结构开始。但是,无论我们使用此类课程创建的内容都不必是完全持久的,也不必提供不可变的接口(例如,可以使用写时复制或“构建器/瞬态”)来实现此功能在我们追求功能/系统/管道的并行性和纯净度的同时,不需增加内存使用量和内存访问量即可复制和修改副本的各个部分。
不变性
最后,我认为这是不变性,这是这三者中最不感兴趣的,但是当某些对象设计不打算用作纯函数的局部临时对象,而是在更广泛的上下文中,有价值时,它可以用铁腕来实现一种“对象级纯度”,因为在所有方法中都不再引起外部副作用(不再在方法的直接局部范围之外对成员变量进行突变)。
尽管我认为这是C ++之类的这三种语言中最不有趣的一种,但它肯定可以简化非平凡对象的测试,线程安全性和推理。例如,保证对象不能在其构造函数之外获得任何唯一的状态组合,并且可以自由地传递它,甚至可以通过引用/指针自由传递它,而不必依赖于常量和读取,这可能是一项负担。仅迭代器和句柄等,同时保证(至少,在我们所能用的语言范围之内)它的原始内容不会被突变。
但是我发现这是最不有趣的属性,因为我认为大多数对象都以临时的形式以可变形式用于实现纯功能(甚至是更宽泛的概念,例如“纯系统”,它可能是一个对象或一系列对象)都是有益的功能仅具有输入和输出新内容而不会触及其他任何东西的最终效果),而且我认为,在很大程度上使用命令式语言使四肢保持不变是一个适得其反的目标。我会在代码库中最有帮助的部分上谨慎地使用它。
最后:
似乎持久性数据结构本身不足以处理一个线程进行的更改对其他线程可见的场景。为此,似乎我们必须使用诸如原子,引用,软件事务性存储器乃至经典锁和同步机制之类的设备。
自然,如果您的设计要求修改(从用户端设计的角度)在多个线程同时发生时对多个线程同时可见,那么我们将返回到同步状态,或者至少是通过绘图板来设计一些复杂的方法来解决此问题(我已经看到了专家处理函数编程中这类问题的一些非常精致的示例。
但是我发现,一旦获得了这种复制并能够廉价输出笨重的结构的部分修改版本,就像使用持久性数据结构作为示例那样,它通常会为您打开很多大门和机会以前从未考虑过在严格的I / O并行管道中并行化可以完全独立运行的代码。即使算法的某些部分本质上必须是串行的,您也可以将处理推迟到单个线程上,但会发现,依靠这些概念已经为轻松并行处理90%的繁重工作打开了大门,例如