我知道Eiffel(前身)和Racket都实现了"契约式设计"的特性。遗憾的是,我不确定其中一个会与另一个有什么不同。Eiffel的DBC依赖于OOP范式和继承,但是Racket这种非常不同的语言如何解释这种差异呢?
球拍出名的主要原因是责备的概念,处理函数是日常球拍编程的重要组成部分,毫无疑问。
您可能还想查看本文的前两个部分:
http://www.ccs.neu.edu/scheme/pubs/oopsla01-ff.pdf首先,在这一点上,您最好的信息来源是球拍指南,这是一个介绍性的文本,而不是一个参考手册。具体来说,书中有一章是关于契约的,这将有所帮助。编辑:也可以看看罗比所指的文件,他是球拍合同的主要负责人。
关于你的问题——我不太了解Eiffel的合同制度,但我认为它先于Racket的制度。然而(这又是一个"IIRC"),我认为Racket的合同系统是第一个引入高阶合同的系统。具体来说,当你处理高阶函数时,分配适当的责任会变得有点复杂——例如,如果你取一个foo
函数,它有一个X? -> Y?
的合约,你给它发送了一个与X?
不匹配的值,那么发送这个值给foo
的客户端代码就会受到指责。但是,如果您的功能是(X? -> Y?) -> Z?
和X?
谓词不满意,那么责任归咎于foo
本身,而不是客户端(如果Y?
不满意,则责任仍然与客户端)。
我想你在问,没有OOP和继承,一个合约系统怎么能工作?作为一个不熟悉Eiffel的Racket用户,我想知道为什么契约系统会与OOP和继承有任何关系。:)
在实践层面上,我认为Racket契约是一种获得静态类型声明的一些好处,同时保持动态类型语言灵活性的方法。+契约不仅仅是类型,还可以充当断言的角色。
例如,我可以说一个函数需要一个参数,是一个精确整数…但也可以说它应该是一个精确的正整数,或者某些特定值的并集,或者实际上是对传递值的任何任意复杂的测试。通过这种方式,在Racket中,契约结合了您在C/c++中(a)类型声明和(b)断言可能做的事情。
在Racket中使用契约的一个缺点是它们可能很慢。解决这个问题的一种方法是在开发时首先使用它们,然后有选择地删除它们,特别是从"内循环"类型的函数中删除它们。我尝试过的另一种方法是批量地打开/关闭它们:创建一对模块,比如contracts-on。RKT和合同终止。后者提供了一些什么都不做的宏。让你的模块需要一个契约。Rkt,它从打开或关闭文件中提供所有内容。这就像在DEBUG和RELEASE模式下编译一样。
如果你是从Eiffel来的,也许我的C/c++对Racket契约的观点不会有帮助,但我还是想分享一下。