我试图了解如何用功能语言实现许多基本的计算机科学概念。我目前无法理解的是功能语言和哲学如何处理内存中的地址。
在诸如排序之类的非常基础的计算机科学概念的背景下,如何有效地处理不变性问题?我知道,确实需要结构共享来防止内存爆炸。但是在我看来,这意味着相对简单的概念(例如选择排序)可能会变得非常复杂。
有人能解释一下功能语言是如何处理的吗?是否放弃了“就地”的想法,并用支持结构共享的数据结构代替了?
我真的在试图了解不变性如何与内存中的地址相适应(认为是指针)。例如,在原位排序数据不会被破坏,但会被移到新地址。这被认为是突变吗?我认为答案是肯定的。但是,您如何才能进行诸如旋转来平衡二叉树的事情呢?函数式程序员如何看待指针?
我知道这是一个相对难以回答的问题,但是对于真正了解功能范式而言,我觉得这是一个大问题。任何见识或资源将不胜感激。
答案 0 :(得分:3)
只是为了解决这个问题:
例如,在原位排序数据不会被破坏,但会被移到新地址。
这没有任何意义。如果将数据“移动到新地址”,则根据定义,该算法将不再“就地”工作。
函数式编程语言由来已久,并没有坚持100%的纯度。从Lisp到ML,再到OCaml,Scala或Clojure,所有这些语言都具有可变的数据结构。在具有功能编程方面的“多范式”语言中,例如JavaScript和Python甚至Java,您还具有可变的数据结构。 Haskell在坚持纯洁方面是一个例外。
大多数功能性编程语言首选持久性数据结构和在不可变数据结构上工作的算法。也就是说,这些语言通常会使用某种平衡排序树,而不是使用可变的哈希图,而不是使用可变列表列表,而是使用不可变的单链接列表。为了对这些列表进行排序,您可以采用合并排序,它可以很好地表示为纯函数程序(但不是就地编写的,至少没有付出很多额外的努力)。
即使您坚持纯洁,也仍然可以将计算机的可变内存当作可变的“外部世界”的另一部分来对待-就像它是某种用户输入输出,系统时钟一样,网络通信或随机数生成器。也就是说,要以纯功能的方式处理可变存储器,您将需要两个组件:首先,您将需要通过构造“计划”来描述可变存储器将要做什么 “-这是不可变的;然后,您将需要一个可以采用此不变方案的解释器,并将其应用于实际可变的内存块。就是说,使存储器发生变异的解释器在某种程度上成为语言核心的外部,并且像“外部可变世界”的任何其他部分一样被对待。
在不要求纯洁的语言中,您可以实现两者这两种特定于领域的小语言,以构造不可变的计划,以及可以真正改变内存的解释器,从而将纯零件与不纯的副作用可变零件分开。例如,Chiusano&Bjarnason在他们的《 Scala中的函数式编程》一书中有14.2.5章字面意思,称为“纯功能性就地快速排序” 。
>通常,在静态类型的函数编程中,不变性本身并不是目标。这样做的目的是确保半支持的可变数据结构不会逃脱算法的狭窄范围,对于可变范围而言,这种结构是有利的。如果您找到一种方法来确保这一点,则意味着您可以编写使用可变内存的纯功能程序。
答案 1 :(得分:1)
您的混乱来自抽象层次的混杂。
如何用您喜欢的OO垃圾收集语言(Python,Java,Ruby等)处理内存分配?你不知道该细节留给编译器和/或运行时。您正在将一种编程语言的语义与实施细节混淆,后者是该语言的编译器。我将允许C / C ++显着模糊这种区别,但是,模糊可能是这些语言目前最显着的特征。
考虑一个通用的关联数据结构C结构:
struct address
{
char number[10];
char street[100];
char city[50];
char state[15];
};
我们事先知道,这在内存中会是什么样子。但是考虑一下Java中类似的数据结构:
public class Record {
public int number;
public String street;
public String city;
public String state;
}
在内存中的布局如何?你不知道即使用字符缓冲区替换了字符串,您也真的不知道。显然,javac实现了它。功能语言中的持久性数据结构也没有什么不同:将内容存入内存的方法取决于编译器,不受编译语言的语义的约束。