自从我第一次使用LINQ以来,我已经趋向于函数式编程已有4年了。最近,我写了一些纯函数式C#代码,并且第一手注意到我所读到的有关函数程序的内容-一旦编译,它们就趋于正确。
我试图指出为什么会这样,但是我没有成功。
一种猜测是,在应用OO主体时,您会拥有一个功能程序中不存在的“抽象层”,并且该抽象层使得在实现错误的情况下对象之间的契约可能正确。
有没有人考虑过这一点,并提出了函数式编程中编译成功与程序正确性之间相关性的根本抽象原因?
final
对所有内容都打耳光)。
自从我第一次使用LINQ以来,我已经趋向于函数式编程已有4年了。最近,我写了一些纯函数式C#代码,并且第一手注意到我所读到的有关函数程序的内容-一旦编译,它们就趋于正确。
我试图指出为什么会这样,但是我没有成功。
一种猜测是,在应用OO主体时,您会拥有一个功能程序中不存在的“抽象层”,并且该抽象层使得在实现错误的情况下对象之间的契约可能正确。
有没有人考虑过这一点,并提出了函数式编程中编译成功与程序正确性之间相关性的根本抽象原因?
final
对所有内容都打耳光)。
Answers:
我可以将这个答案写成可以证明很多事情的人,所以对我而言,正确性不仅在于有效,还在于有效且易于证明。
从很多意义上说,函数式编程比命令式编程更具限制性。毕竟,没有什么能阻止您从不更改C中的变量!实际上,FP语言中的大多数功能都可以直接用几个核心功能来谈论。一切都归结为lambda,函数应用程序和模式匹配!
但是,由于我们已经提前支付了吹笛者的费用,因此我们要处理的事情要少得多,而且事情出错的可能性也要少得多。如果您是1984年的粉丝,自由确实是奴隶制!通过为程序使用101种不同的巧妙技巧,我们必须对事物进行推理,好像这101种事物中的任何一种都可能发生!事实证明,这确实很难做到:)
如果从安全剪刀开始而不是剑开始,则跑步的危险性相对较小。
现在我们来看您的问题:所有这些如何适应“它的编译和运行!” 现象。我认为其中很大一部分原因与证明代码容易的原因相同!毕竟,当您编写软件时,您正在构造一些非正式的证明它是正确的。因此,您的自然波状证明涵盖了这些内容,并且编译器拥有正确性(类型检查)的概念。
当您添加功能以及它们之间的复杂交互时,类型系统未检查的内容会增加。但是,您构造非正式证明的能力似乎并没有提高!这意味着还有更多可以通过初始检查的内容,必须在以后进行检查。
函数式编程中编译成功与程序正确性之间相关性的根本抽象原因?
可变状态。
编译器会静态检查内容。它们确保您的程序格式正确,并且类型系统提供了一种机制,用于尝试确保在正确的位置允许使用正确的值。类型系统还试图确保在正确的位置允许使用正确的语义。
一旦您的程序引入状态,后一个约束就不再有用。您不仅需要担心正确位置的正确值,而且还需要考虑在程序的任意点更改该值的情况。您需要考虑与该状态一起更改的代码的语义。
如果您很好地进行了函数编程,则没有(或很少)可变状态。
但是,这里有一些因果关系的争论-如果没有状态的程序在编译后更频繁地工作,因为编译器可以捕获更多的错误,或者如果没有状态的程序在编译后更频繁地工作,因为这种编程风格产生的bug更少。
根据我的经验,可能两者兼而有之。
简而言之,这些限制意味着将事物放在一起的正确方法较少,而一流的功能使它更容易排除诸如循环结构之类的事物。以这个答案的循环为例:
for (Iterator<String> iterator = list.iterator(); iterator.hasNext();) {
String string = iterator.next();
if (string.isEmpty()) {
iterator.remove();
}
}
这恰好是Java中在迭代过程中从集合中删除元素的一种安全的必要方法。有很多方法看起来很接近,但是都错了。人们不知道这种方法有时会采用复杂的方法来避免该问题,例如遍历一个副本。
使其通用并不困难,因此它不仅可以用于的集合Strings
,而且还可以用于一流的函数,因此您无法替换谓词()中的条件if
,因此该代码倾向于被复制和粘贴。并稍作修改。
结合一流的函数,这些函数使您能够将谓词作为参数传递,并且具有不变性的限制,如果不这样做,则非常烦人,并且您会想到简单的构建基块filter
,例如此Scala代码做同样的事情:
list filter (!_.isEmpty)
现在考虑一下在编译时使用Scala的类型系统为您检查的内容,但是这些检查也由动态类型系统在您首次运行时进行:
list
必须是支持该filter
方法的某种类型,即集合。list
必须具有isEmpty
返回布尔值的方法。一旦检查完这些内容,程序员还有什么其他方法可以解决?我不小心忘记了!
,导致了非常明显的测试用例失败。那几乎是唯一可以犯的错误,而我之所以犯了这个错误,是因为我是直接从测试了逆条件的代码中翻译出来的。
一遍又一遍地重复这种模式。一流的功能使您可以将它们重构为具有精确语义的小型可重用实用程序,诸如不变性之类的限制使您有这样做的动力,并且对这些实用程序的参数进行类型检查几乎没有余地来固定它们。
当然,这全都取决于程序员知道简化功能filter
已经存在,能够找到它或认识到创建自己的好处的程序员。尝试仅使用尾部递归在任何地方自己实现这一点,而您又回到了与命令式版本相同的复杂性中,更糟糕的是。仅仅因为您可以非常简单地编写它,并不意味着简单的版本显而易见。
我认为函数式编程编译与运行时正确性之间没有显着相关性。静态类型的编译和运行时正确性之间可能存在某种关联,因为如果您不进行强制转换,至少您可能拥有正确的类型。
正如您所描述的,可能以某种方式将成功的编译与运行时类型正确性相关联的编程语言方面是静态类型,即使如此,前提是您不使用只能在运行时声明的强制转换来弱化类型检查器(在具有强类型值或强类型(例如Java或.Net)或根本不强(在类型信息丢失或弱类型(例如C和C ++)的环境中)。
但是,函数式编程本身可能会以其他方式提供帮助,例如避免共享数据和可变状态。
这两个方面在正确性上可能都具有显着的相关性,但是您必须意识到,没有编译和运行时错误,从严格意义上讲,从更广泛的意义上讲,它并不能告诉您正确性,因为在程序中,它按照预期执行并且快速失败。无效输入或无法控制的运行时故障。为此,您需要业务规则,需求,用例,断言,单元测试,集成测试等。最后,至少在我看来,它们提供的功能比功能编程,静态类型输入或二者兼有。
经理说明:
一个功能程序就像一台大型机器,其中连接了所有东西,管道,电缆。[一辆车]
程序程序就像一栋建筑物,其中的房间里装有一台小型机器,将部分产品存储在垃圾箱中,并从其他地方获取部分产品。[一个工厂]
因此,当功能机器已经装配在一起时,它必然会产生一些东西。如果运行了一个复杂的程序,您可能会监督特定的效果,造成混乱,而不能保证其正常运行。即使您拥有所有已正确集成的清单,也有很多状态,可能的情况(部分产品散落,水桶满溢,丢失),因此很难提供保证。
但是,严重的是,过程代码没有像功能代码那样指定期望结果的语义。过程程序员可能更容易摆脱周围的代码和数据,并介绍了完成一件事情的几种方法(其中有些方法不完善)。通常,会创建无关的数据。当问题变得更加复杂时,函数式程序员可能需要更长的时间?
强类型的功能语言仍然可以进行更好的数据和流分析。使用过程语言时,通常必须在程序外部定义程序的目标,作为形式正确性分析。