我正在用C ++构建一个聚类算法,但是我不能很好地处理OOP和变化的变量状态(成员数据)。对于一些复杂的算法,我发现这是我开发的一个障碍。
所以,我正在考虑将编程语言改为其中一种函数语言:Ocaml或F#。除了必须改变我对如何进行编程的思维方式之外,我还需要澄清一些问题。在C ++中,我使用双端队列来滑动数据中的时间窗口。一段时间后,将删除最旧的数据并附加更新的数据。尚未过时的数据仍保留在双端队列中。
另一项要求更高的任务是比较每个对象之一的属性。每个对象都是来自特定时间段的数据。如果我在某个时间窗口有一千个数据对象,我需要比较每个数据对象之间没有,或者二十或三十,取决于。并且该比较对象的某些属性可能会因此比较而发生变化。在C ++中,我使用引用完成所有操作,这意味着我访问内存中的对象,它们永远不会被复制,因此算法全速运行(对于我的C ++知识)。
我一直在阅读有关函数式编程的知识,我得到的想法是每个函数执行一些操作并且原始数据(输入)不会改变。这意味着该语言复制数据结构并执行所需的转换。如果是这样,使用函数式编程将大大延迟算法的执行。它是否正确?如果没有,即如果有快速的方法来执行数据转换,是否可以告诉我如何操作?一个很小的例子就是很棒。
我希望有某种设施。我读过Ocaml和F#都用于研究和科学项目。
答案 0 :(得分:8)
在高级别,您的问题是使用不可变数据是否比使用可变数据慢。答案是肯定的,在某些情况下它会更慢。令我感到惊讶的是(对我来说)惩罚是多么小。在大多数情况下(根据我的经验),额外的时间(通常是对数因子)值得使用不可变数据的额外模块化和清晰度。在许多其他情况下,根本没有惩罚。
它没有你想象的那么慢的主要原因是你可以自由地重用旧数据的任何部分。没有必要担心计算的其他部分将在以后改变数据:它是不可变的!
由于类似的原因,所有对不可变数据的访问都类似于C ++中的引用。由于计算的其他部分无法更改数据,因此无需复制数据。
如果您想以这种方式工作,则需要构建数据以重新使用。如果你不能轻易做到这一点,你可能想要使用一些(受控制的)突变。
OCaml和F#都是混合范式语言。如果您愿意,它们允许您使用可变数据。
关于不可变数据(IMHO)操作的最具启发性的说明是Chris Okasaki的书Purely Functional Data Structures。 (此亚马逊链接仅供参考,不一定是购买书籍的建议:-)您也可以在Okasaki Phd thesis找到大部分信息。
答案 1 :(得分:5)
你绝对可以在OCaml和F#中实现pointer machine。这样您就可以存储直接引用并更新它们。如,
type 'a cell = {
data : 'a;
mutable lhs : 'a cell;
mutable rhs : 'a cell;
}
在OCaml中,这将表示为指向数据结构的指针,包含三个单词:指向数据的指针和指向兄弟节点的两个指针:
+--------+ +-------+ +-------+
| cell |-------->| data |----->| |
+--------+ |-------| +-------+
+---| lhs |
| |-------|
| | rhs |--+
| +-------+ |
| +-------+ | +-------+
+-->| data | --->| data |
|-------| |-------|
| lhs | | lhs |
|-------| |-------|
| rhs | | rhs |
+-------+ +-------+
所以,这里没什么特别的。它是相同的,因为您可以在C ++中选择持久性和命令式实现。但是在C ++中,由于缺乏对语言本身的支持,你通常会为持久性付出更大的代价。在OCaml中有一个生成垃圾收集器,具有非常便宜的分配成本和其他优化。
所以,是的,您可以以常规(命令性)方式实现数据结构。但在此之前,你必须非常肯定,你已准备好为此付出代价。理解功能代码要容易得多,而不是命令式。这实际上是人们选择和使用FP范式的主要原因。
答案 2 :(得分:2)
这意味着该语言复制数据结构并执行所需的转换
不一定。如果对象是不可变的(因为它们默认为F#记录类型,在C ++中,如果所有数据成员都是const
而没有使用mutable
),则可以使用引用。
如果是这样,使用函数式编程将大大延迟算法的执行。这是对的吗?
即使具备上述功能,功能语言也倾向于支持延迟操作。在F#中,使用正确的数据结构/方法,情况就是如此。但它也可能很渴望。
一个例子(不是可怕的惯用语,但试图说清楚):
let Square (is : seq<'t>) = is |> Seq.map(fun n -> n*n)
然后在
let res = [1; 2; 3; 4] |> Square
在您从re
读取值之前,不会计算任何方格。
答案 3 :(得分:1)
从两个因素来理解这一点非常重要:突变和共享。 你(似乎)专注于突变方面,似乎忽略了分享。
采取标准列表 - 追加&#39; @&#39 ;;它复制左arg并分享正确的
所以,是的,你通过复制而相应地失去了效率 通过分享获得。因此,如果您安排数据结构以最大化共享 你可以从因不可变性造成的损失中获益。
在大多数情况下,这种情况恰好发生在&#39;。但有时你需要调整它。
在haskell中涉及懒惰的常见例子:
ones = 1 : ones
这表示1的无限列表[1,1,1,...]
并且实施可以
期望将其优化为循环(圆形图)
+-----------+
| |
V |
+---------+ |
| | |
| 1 |-->---+
| |
+---------+
然而,当我们将它推广到无限的x-es列表
时repeat x = x : repeat x
实现有更难的时间检测循环,因为
变量ones
现在已成为(递归)函数调用repeat x
将其更改为
repeat x = let repeat_x = x : repeat_x in repeat_x
并恢复循环(即共享)。