充满异国情调的编程理念:模块系统

2020-11-14 00:18:16

所有成功的编程语言都是相似的;每种不成功的编程语言都有自己的不成功之处。

软件的历史是与同时但独立的对话并行的,这就是我们如何看待计算以及我们如何与其他人类交流这些想法的故事。计算的故事一直是关于这种非常新颖和独特的人类表达形式的进化,我们称之为代码。我怀疑在21世纪成为一名程序员肯定就像是在公元前3200年成为一名皇家抄写员的古埃及一样。有一种新的沟通方式,大多数人都不知道,但它的存在同时使商业、文化和文明蓬勃发展。

就像自然语言一样,我们已经看到了语义和句法的融合,就像欧洲语言的拉丁语一样。这些语言属于受C语言启发的命令式范例,具有一些面向对象的特性。这描述了C++、Go、Python、Ruby、Rust、Java和Javascript等语言,它们共同构成了当今编写的所有新代码的主体。虽然大多数编程活动已经汇聚到这一本地生产力的最大值上,但我们编程的边缘也有一些想法,它们包含了更多深奥的想法,其语义和语法与主流程序中的想法截然不同。

在我的职业程序员生涯中,我意识到有两类不同的程序员。那些将编程语言主要视为人类理性工具的人,以及那些将其视为特定任务的生产工具的人。就像很少有有限的人会说Pirahão、Navajo、Klingon或Berik一样;也有一些编程语言的采用率同样有限。然而,这些语言编码了一些非常有趣的语义结构,当我们试图在主流语言中用它们编码时,这些结构往往会在翻译中丢失。

在我的基督降临博客中(因为我感到无聊和被封锁),我要写关于软件文化边缘的七个语言语义特征。这些人有着古怪而狂野的想法,如果罗马人没有征服,他们会带你踏上一段可能会是什么样子的旅程。

让我们从模块化系统和模块化编程开始。模块的主要思想是将代码分解成称为模块的可重用组件。模块作为一种语言特性最早是在MODLA-2和PASCAL中开发的,它们是作为划分编译单元的一种方式开发的。这个概念在1984年的标准ML语言中成熟起来,它进一步发展了这个概念,并允许模块的抽象和参数化。概念。如今,完整的模块系统在ML家族中的语言中都可以找到,比如F#、OCaml、Standard ML,尽管其他一些语言,比如AGDA也有这些语言。我们将介绍OCaml,它是一种带有类型推断的静态类型的ML方言。

模块中包含的信息可以是值,也可以是类型。例如,类型t和单个自变量平方的函数。

模块MyModule=struct type t=int let Square x=x*x end

模块的组件可以绑定到称为签名的描述,该描述既约束了模块中符号的可见性,又强制了整个模块之间的一致接口。

模块MyModule:SigVal Square:int->;int end=struct let square x=x*x end。

或者,模块签名可以在类型级别绑定到特定名称,并使用模块类型语法跨多个模块独立定义。这与规范和实现是分开的。在这里,我们定义了一个抽象类型t,它可以抽象地在签名中引用,以引用其最终实例化的具体类型。术语s在本说明书中被定义为“类型为t的值”。

(*MySig规范*)模块类型MySig=sig(*类型签名集*)type t val s:t end(*MySig实现*)模块MyModule:MySig=struct(*定义集*)type t=int let s=0 end。

可以使用OPEN语法打开模块。如果在顶层使用,这将在给定范围内或全局范围内将模块内的所有公开符号作用域。

模块也可以投射到使用点语法来检索模块作用域中的特定符号。例如:`。

模块的签名不一定要约束模块中定义的所有符号。签名可以密封特定参数或类型的实现,这些参数或类型的实现细节位于模块内部,并且不会公开。例如,在下面的模块中,Hello签名隐藏了Hello函数使用的消息,并且不允许下游用户修改消息本身的内部结构,只允许调用该函数。

模块类型Hello=sig Val Hello:Unit->;Unit End模块Iml:Hello=struct(*Private Variable Message*)let Message=";let Hello()=print_endline Message end;;impl.Hello();;

模块本身也可以嵌套在任意深度的其他模块中。在本例中,投射到外部模块可以检索内部模块及其内容:

模块外部=结构令a=1模块内部=结构令b=2结束令c=外部.内部.B;;

除了嵌套之外,模块还可以包括其他模块的内容,方法是在模块定义内临时将它们的值限定在作用域中,或者使用INCLUDE语法将给定模块作用域的内容复制到新定义中。

模块A=struct let a=10 end模块B=struct let b=20 end模块Arith1=struct Include A Include B let c=a+b end模块Arith2=struct open A open B let c=a+b end;;Arith1.a;;Arith2.a;;(*不在范围内*)。

模块本身可以通过类型(包括其他模块)进行参数化。这些函数称为函数器,允许我们通过指定给定模块参数必须符合的接口来实例化模块。在该示例中,参数M是符合签名S的抽象模块。该签名由给定模块A实现,该实现可以作为参数M在FA的实例化中传递。在F的定义中,我们投影到M以抽象地检索s和t参数。

模块类型S=sig type t val s:t end模块A:s=struct type t=int let s=0 end(*unctor*)模块F(M:s)=struct type a=M.T let b=M.s end(*F应用于模块A*)模块FA=F(A);

通常,对同一函数器的两次调用将产生包含相同抽象类型的模块。但是,我们可以定义另一类函数器,称为生成函数器,它由一个额外的参数()表示,它将产生一个包含不相等抽象类型的输出模块。在本例中,F和G的输出类型是不同的。在处理需要突变和唯一性的引用时,这一点尤为重要。

模G(M:S)()=结构型a=M.T令b=M.s端模GA=G(A)();