函数式编程、声明式编程和命令式编程是什么意思?
对于这些并没有明确客观的定义。下面是我如何定义它们:
必要的 -重点是计算机应该采取什么步骤,而不是计算机将做(例如C, c++, Java)。
声明 -重点是计算机应该做什么,而不是它应该怎么做(例如SQL)。
功能 -一个声明性语言的子集,非常关注递归
我认为你的分类是不正确的。有两种相反的类型,命令式和声明式。函数式只是声明式的一个子类型。顺便说一句,维基百科也说了同样的事实。
简而言之:
命令式语言指定了计算机按顺序执行的一系列指令(做这个,然后做那个)。
声明性语言声明了一组规则,关于哪些输入应该产生哪些输出(例如:如果你有A,那么结果就是B).引擎会将这些规则应用于输入,并给出输出。
函数式语言声明了一组数学/逻辑函数,这些函数定义了如何将输入转换为输出。如。F (y) = y * y.它是一种声明性语言。
简而言之,一种编程风格越是强调“做什么”(What (to do))而忽略了“如何做”(How (to do))的细节,这种风格就越被认为是声明性的。而imperative则相反。函数式编程与声明式风格相关联。
声明性编程是通过在输入和输出之间表达一些永恒的逻辑来编程,例如,在伪代码中,下面的例子将是声明性的:
def factorial(n): if n < 2: return 1 else: return factorial(n-1) output = factorial(argvec[0])
我们只是在这里定义了一个叫做“阶乘”的关系,并将输出和输入之间的关系定义为那个关系。这里应该很明显,几乎任何结构化语言都在某种程度上允许声明性编程。声明式编程的核心思想是不可变数据,如果你给一个变量赋值,你只赋值一次,然后就再也不会赋值了。其他更严格的定义要求可能根本没有副作用,这些语言有时被称为“纯声明性”。
在命令式风格中,同样的结果将是:
a = 1 b = argvec[0] while(b < 2): a * b-- output = a
在这个例子中,我们没有在输入和输出之间表达永恒的静态逻辑关系,我们手动更改内存地址,直到其中一个保存所需的结果。很明显,所有语言在某种程度上都允许声明性语义,但并非所有语言都允许命令式语义,一些“纯”声明性语言允许副作用和突变。
声明性语言通常被认为是指定“必须做什么”,而不是“如何做”,我认为这是一个用词不当,声明性程序仍然指定必须如何从输入到输出,但在另一种方式下,你指定的关系必须是有效的可计算的(重要术语,如果你不知道它,请查阅它)。另一种方法是不确定的编程,它实际上只是指定结果满足什么条件,然后你的实现就会耗尽所有的路径,直到它成功。
纯声明性语言包括Haskell和Pure Prolog。从一种到另一种的比例可以是:Pure Prolog, Haskell, OCaml, Scheme/Lisp, Python, Javascript, C——,Perl, PHP, c++, Pascall, C, Fortran, Assembly
命令式编程意味着任何一种编程风格,其中你的程序是由指令描述计算机执行的操作如何发生的构造的。
声明性编程意味着任何一种编程风格,其中你的程序是对问题或解决方案的描述——但是没有明确说明工作将如何完成。
函数式编程是通过计算函数和函数的函数来编程的…因为(严格定义)函数式编程是指通过定义无副作用的数学函数来编程,所以它是声明式编程的一种形式,但它不是唯一一种声明式编程。
逻辑编程(例如在Prolog中)是另一种形式的声明式编程。它包括通过决定一个逻辑语句是否为真(或是否可以满足它)来计算。程序通常是一系列事实和规则——即描述,而不是一系列指令。
项重写(例如CASL)是另一种形式的声明式编程。它涉及代数项的符号变换。它完全不同于逻辑编程和函数式编程。
必要的和声明描述了两种相反的编程风格。命令式是传统的“循序渐进”方法,而声明式则更多地是“这就是我想要的,现在你来研究如何去做”。
这两种方法贯穿于整个编程过程——即使使用相同的语言和相同的程序。一般来说,声明式方法被认为是更可取的,因为它使程序员不必指定如此多的细节,同时也减少了出现错误的机会(如果您描述了您想要的结果,并且一些经过良好测试的自动流程可以从该结果向后工作以定义步骤,那么您可能希望事情比手工指定每个步骤更可靠)。
另一方面,命令式方法为您提供了更多的低级控制——这是编程的“微观管理方法”。这可以让程序员利用有关问题的知识来给出更有效的答案。因此,程序的某些部分以声明式的风格编写并不罕见,但对速度至关重要的部分则更加必要。
正如你可能想象的那样,你用来写程序的语言会影响你的声明性——一种具有内置“智能”的语言,在给出结果描述的情况下,它将允许一种更声明性的方法,而不是程序员需要先用命令式代码添加这种智能,然后才能在上面构建更具声明性的层。例如,像prolog这样的语言被认为是非常声明性的,因为它内置了一个搜索答案的过程。
到目前为止,你会注意到我还没有提到功能编程。这是因为它的含义与其他两个没有直接联系。函数式编程最简单的意思是使用函数。特别是,您使用一种支持函数作为“第一类值”的语言——这意味着您不仅可以编写函数,而且可以编写编写函数的函数(编写编写…函数的函数),并将函数传递给函数。简而言之,函数就像字符串和数字一样灵活和常见。
因此,函数式、命令式和声明式经常被一起提到,这似乎很奇怪。其原因是将函数式编程的思想“发挥到极致”的结果。函数,从最纯粹的意义上讲,是一种来自数学的东西——一种“黑盒子”,它接受一些输入,并总是给出相同的输出。这种行为不需要存储变化变量。因此,如果你设计一种编程语言,其目标是实现一种非常纯粹的、受数学影响的函数式编程思想,那么你最终会在很大程度上拒绝可以改变的值的思想(在某种特定的、有限的、技术意义上)。
如果你这样做——如果你限制变量的改变方式——那么你几乎会意外地迫使程序员编写更具声明性的程序,因为命令式编程的很大一部分是描述变量如何改变,而你再也不能这样做了!因此,函数式编程——特别是用函数式语言编程——倾向于给出更多的声明性代码。
总结一下:
命令式和声明式是两种相反的编程风格(鼓励这些风格的编程语言使用相同的名称)
函数式编程是一种编程风格,其中函数变得非常重要,因此,更改值变得不那么重要。在值中指定更改的有限能力迫使使用更声明性的风格。
因此,“函数式编程”通常被描述为“声明式的”。
命令:< em > < / em >如何来实现我们的目标
Take the next customer from a list. If the customer lives in Spain, show their details. If there are more customers in the list, go to the beginning
声明性:< em > < / em >我们想要实现
Show customer details of every customer living in Spain
在写这篇文章的时候,本页上投票最多的答案在声明式和命令式的定义上是不精确的,包括引用维基百科的答案。一些答案是用不同的方式合并了这两个术语。
另请参阅我的解释,了解为什么电子表格编程是声明性的,而不管公式是否改变了单元格。
另外,有几个答案声称函数式编程必须是声明式编程的一个子集。在这一点上,取决于我们是否区分“功能”。从“procedure"。让我们先处理命令式和声明式。
定义陈述性表达式
只有属性可以区分声明表达式和必要的表达式,它是其子表达式的引用透明度(RT)。所有其他属性要么在两种类型的表达式之间共享,要么从RT派生。
100%声明性语言(即每一个可能的表达式都是RT的语言)不允许(在其他RT要求中)对存储值进行突变,例如HTML和大部分Haskell。
定义RT表达式
RT通常被称为“无副作用”。影响这个词并没有一个精确的定义,所以有些人不同意“没有副作用”;与RT相同。RT有精确的定义:
表达式e是引用透明的,如果对于所有程序p, e在p中出现的每一个e都可以被计算e的结果所取代,而不影响p的可观察结果。
e
p
由于每个子表达式在概念上都是函数调用,RT要求函数的实现(即被调用函数中的表达式)不能访问函数的外部可变状态(允许访问可变的local state)。简单地说,函数(实现)应该是纯。
定义纯函数
纯功能通常被认为是“无副作用”的。影响这个词没有一个精确的定义,所以有些人不同意。
纯函数具有以下属性。
记住,RT适用于表达式(包括函数调用),而purity适用于函数的(实现)。
生成RT表达式的不纯函数的一个模糊示例是并发,但这是因为在中断抽象层,纯函数被打破了。你不需要知道这些。要生成RT表达式,需要调用纯函数。
RT的导数属性
为声明式编程引用的任何其他属性,例如维基百科使用的引用自1999年,要么来自RT,要么与命令式编程共享。从而证明我的精确定义是正确的。
注意,外部的价值观的不可变性是RT需求的一个子集。
for
while
声明性语言表达逻辑“步骤”;(即嵌套的RT函数调用顺序),但每个函数调用是否是更高层次的语义(即“做什么”)并不是声明式编程的要求。与命令式的区别在于因为不变性(即更普遍的RT),这些“步骤”;不能依赖于可变状态,而只能依赖于所表达逻辑的关系顺序(即函数调用的嵌套顺序,也称为子表达式)。
例如,HTML段落<p>不能显示,直到段中的子表达式(即标签)被求值。没有可变状态,只有由于标记层次结构的逻辑关系而产生的顺序依赖(子表达式嵌套,即类似地嵌套函数调用)。
<p>
评估顺序
子表达式求值顺序的选择只能在任何函数调用不是RT(即函数不是纯函数)时给出不同的结果,例如在函数内部访问函数外部的某些可变状态。
例如,给定一些嵌套表达式,例如f( g(a, b), h(c, d) ),如果函数f、g和h是纯函数,则对函数参数的热切求值和惰性求值将给出相同的结果。
f( g(a, b), h(c, d) )
f
g
h
然而,如果函数f, g和h不是纯函数,则计算顺序的选择可能会给出不同的结果。
注意,嵌套表达式在概念上是嵌套函数,因为表达式操作符只是伪装成一元前缀、一元后缀或二进制中缀表示法的函数调用。
切题地说,如果所有的标识符,例如a, b, c, d,到处都是不可变的,程序外部的状态不能被访问(即I/O),并且没有抽象层破坏,那么函数总是纯的。
a
b
c
d
顺便说一下,Haskell有一个不同的语法,f (g a b) (h c d)。
f (g a b) (h c d)
评估订单详情
函数是从输入到输出的状态转换(不是可变的存储值)。对于纯函数调用的RT组合,这些状态转换的执行顺序是独立的。每个函数调用的状态转换是独立于其他函数调用的,这是由于没有副作用和RT函数可以被其缓存的值替换. c调用的原则。对于纠正一个流行的误解,纯粹的单元组合是always declarative and RT,尽管Haskell的IO单元是可以说是不洁净的,因此必须在程序外部w.r.t. World状态(但根据下面的警告,副作用是隔离的)。
IO
World
主动求值意味着函数参数在函数被调用之前被求值,而惰性求值意味着参数不计算直到(和if)在函数中被访问。
定义:函数参数在函数定义站点声明,函数参数在函数调用站点提供。了解参数和论点之间的区别。
从概念上讲,所有表达式都是函数调用的(组合),例如常量是没有输入的函数,一元操作符是只有一个输入的函数,二进制中子星操作符是有两个输入的函数,构造函数是函数,甚至控制语句(例如if, for, while)都可以用函数建模。for0 for1函数(不要与嵌套函数调用顺序混淆)被求值不是由语法声明的,例如,f( g() )可以在g的结果上立即求值g,然后再求值f,或者它可以求值f,而只在f中需要其结果时才延迟求值g。
if
f( g() )
注意,没有任何图灵完全的语言(即允许无界递归的语言)是完全声明性的,例如,惰性求值引入了内存和时间不确定性。但是由于求值顺序的选择,这些副作用仅限于内存消耗、执行时间、延迟、非终止和外部磁滞,因此是外部同步。
函数式编程
因为声明式编程不能有循环,所以迭代的唯一方法是函数递归。正是在这个意义上,函数式编程与声明式编程相关。
但函数式编程并不局限于声明式编程。函数组合可以是与子类型相比,特别是关于表达式问题,其中扩展可以通过要么添加子类型,要么进行功能分解实现。两种方法的扩展可以是一种混合。
函数式编程通常使函数成为一级对象,这意味着函数类型可以出现在语法中任何其他类型可能出现的地方。结果是函数可以输入函数并对函数进行操作,从而通过强调函数组合来提供关注点分离,即分离确定性计算的子计算之间的依赖关系。
例如,而不是写一个单独的函数(如果函数也必须是声明性的,则使用递归而不是循环)对于可以应用于集合的每个元素的无限个可能的专门操作,函数式编程使用可重用的迭代函数,例如map, fold, filter。这些迭代函数输入一个一级专门的动作函数。这些迭代函数迭代集合,并为每个元素调用输入专门化操作函数。这些动作函数更加简洁,因为它们不再需要包含循环语句来迭代集合。
map
fold
filter
但是,请注意,如果一个函数不是纯函数,那么它实际上是一个过程。我们也许可以认为,使用非纯函数的函数式编程实际上是过程式编程。因此,如果我们同意声明式表达式是RT,那么我们可以说过程式编程不是声明式编程,因此我们可能会认为函数式编程始终是RT,并且必须是声明式编程的子集。
并行性
这个具有一级函数的函数组合可以通过分离出独立函数来表示平行的深度。
布伦特原理:计算用功w和深度d可以 在p-处理器PRAM时间O(max(w/p, d))中实现。
并发性和并行性也要求声明式编程,即不可变性和RT。
那么,并行性=并发性这个危险的假设在哪里呢 从何而来?这是带有副作用的语言的自然结果: 当你的语言到处都有副作用时,那么任何时候你尝试 一次做不止一件事 由各因素相互作用而引起的不确定性 操作。所以用副作用的语言来说,唯一的方法 并行性是并发性;因此,我们
注意,求值顺序还会影响功能组合的终止和性能副作用。
Eager (CBV)和lazy (CBN)是分类决斗[10],因为它们有相反的求值顺序,即外部函数或内部函数分别先求值。想象一个上下颠倒的树,然后从函数树分支求值,从分支层次结构向上到顶层函数主干;而lazy则从主干计算到分支尖端。Eager没有连词产物(&;and", a/k/一个直言的“products"), lazy没有析取的副产物(&;or", a/k/一个直言的“sum ")[11]。
性能
与non- terminate一样,eager对于连接功能组合来说过于急切,即组合控制结构做了lazy无法完成的不必要的工作。对于例子,当它由一个终止于第一个真元素的fold组成时,eager急切而不必要地将整个列表映射到布尔值。
这种不必要的工作是索赔“到”的原因;eager和lazy的顺序时间复杂度中额外的Log n因子,两者都是纯函数。一种解决方案是使用函子(例如,列表)和惰性构造函数(例如,eager和可选的惰性乘积),因为使用eager时,eager的错误源于内部函数。这是因为积是构造类型,即在初始固定点上具有初始代数的归纳类型[11]
与非终止一样,lazy对于析取函数组合来说太过懒惰,即共归纳终结可能发生在必要的时间之后,导致不必要的工作和延迟的非确定性,而eager[10][11]则不是这种情况。终结性的例子有状态异常、定时异常、非终止异常和运行时异常。这些都是命令式的副作用,但即使在纯声明性语言(例如Haskell)中,在空间分配中也隐含着命令式IO单子中的状态(注意:并非所有单子都是命令式的!),而计时是相对于命令式现实世界的状态。即使使用lazy和可选的eager副产物,也会泄漏“lazy”。转换为内积,因为对于lazy,惰性错误来源于外部函数(请参阅非终止部分中的示例,其中==是一个外部二进制操作符函数)。这是因为协积受最终性的限制,即在最终对象[11]上具有最终代数的共归纳类型。
Lazy会导致延迟和空间函数的设计和调试中的不确定性,调试可能超出了大多数程序员的能力,因为之间的失调声明的函数层次结构和运行时求值顺序。使用eager计算的惰性纯函数可能会在运行时引入以前未见的非终止。相反,用lazy来计算的eager纯函数可能会在运行时引入之前未见的空间和延迟不确定性。
Non-termination
在编译时,由于图灵完备语言中的停止问题和相互递归,函数通常不能保证终止。
With eager but not lazy, for Head "and"Tail,如果Head或Tail不终止,则分别List( Head(), Tail() ).tail == Tail()或List( Head(), Tail() ).head == Head()不为真,因为左边不终止,而右边终止。
Head
Tail
List( Head(), Tail() ).tail == Tail()
List( Head(), Tail() ).head == Head()
而对于lazy,两边都终止。因此,对于连接产品,eager太过急切,在那些不需要它的情况下使用非终止符(包括运行时异常)。
用懒而不急,换1 "or"2,如果f不终止,则List( f ? 1 : 2, 3 ).tail == (f ? List( 1, 3 ) : List( 2, 3 )).tail不为真,因为左边终止,而右边不终止。
1
2
List( f ? 1 : 2, 3 ).tail == (f ? List( 1, 3 ) : List( 2, 3 )).tail
然而,由于渴望,任何一方都不终止,所以相等检验永远达不到。因此lazy懒得处理析取的副产品,在这些情况下,在做了比eager更多的工作后未能终止(包括运行时异常)。
[10]声明性延续和范畴二元性,Filinski,第2.5.4节CBV和CBN的比较,以及3.6.1 CBV和CBN在SCL中。
[11]声明性延续和类别对偶性,Filinski,第2.2.1节产物和副产物,2.2.2终端和初始对象,2.5.2带惰性产物的CBV,和2.5.3带渴望副产物的CBN。
必要的 -表达式描述要执行的动作序列(关联)
声明表达式是对程序行为有贡献的声明(结合的,交换的,幂等的,单调的)
功能 -表达式的唯一作用是价值;语义支持等式推理
因为我写了之前的答案,我已经制定了声明性属性的新定义,下面引用了它。我还将命令式编程定义为对偶属性。
这个定义比我在之前的回答中提供的定义更优越,因为它更简洁,更普遍。但它可能更难以理解,因为适用于编程和一般生活的不完备定理的含义对人类来说是很难理解的。
引用的定义解释讨论了纯函数式编程在声明式编程中所扮演的角色。
所有外来的编程类型都符合以下声明式和命令式的分类,因为下面的定义声称它们是二元的。
声明式与命令式 声明性属性是奇怪的、迟钝的,并且很难在技术上精确的定义中捕获,这种定义仍然是通用的且不含糊的,因为它是一种天真的概念,即我们可以声明程序的意义(又称为语义)而不会引起意想不到的副作用。在表达意义和避免意想不到的效果之间有一种内在的紧张关系,这种紧张关系实际上来自编程和我们的宇宙的不完备定理。 将声明式定义为“< em > what 用< / em >”而将命令式定义为“< em >请;用< / em >”是过度简化、技术上不精确的,而且通常是模棱两可的。“什么”是输出编译器的程序中的“如何”,这是一种模棱两可的情况。 显然,无限递归,使语言图灵完备在语义上也是类似的——不仅仅是在求值的语法结构中(也就是操作语义)。这在逻辑上是一个类似于Gödel的定理-“任何完整的公理系统也是不一致的”的例子。想想这句话自相矛盾的诡异之处吧!它也是一个例子,说明了语义的表达式如何没有可证明的边界,因此我们无法证明__abc2一个程序(类似于它的语义)停止,也就是停止定理。 不完备性定理源于我们宇宙的基本性质,正如热力学第二定律所述,它是“熵(也就是独立可能性的#)趋势是永远的最大值吗”。程序的编码和设计永远不会结束——它是活的!——因为它试图解决现实世界的需求,而现实世界的语义总是在变化,并趋向于更多的可能性。人类从未停止发现新事物(包括程序中的错误;-)。 为了在这个没有边界的奇怪宇宙中精确地和技术地捕捉上述所期望的概念(想想吧!我们的宇宙没有“外部”),需要一个简洁但看似不简单的定义,除非深入解释,否则听起来是不正确的。 定义: 声明性属性是指只能存在一组可能的语句来表达每个特定的模块语义。 命令式属性__abc0是对偶,其中语义在组合下是不一致的,并且/或可以用语句集的变化来表示。 声明性的这个定义在语义范围内是独特的当地的,这意味着它要求模块化语义保持其一致的含义,而不管它在全球范围内实例化和使用的位置和方式。因此,每个声明性模块语义本质上应该与所有可能的其他语义正交——而不是一个不可能(由于不完备定理)的全球算法或模型来见证一致性,这也是罗伯特·哈珀(卡内基梅隆大学计算机科学教授,标准ML的设计师之一)的“更多并不总是更好”的观点。 这些模块化声明性语义的例子包括范畴理论函子,例如的Applicative、名义类型、命名空间、命名字段和w.r.t.,然后是纯函数编程的语义操作级。 因此,设计良好的声明性语言可以更清楚地表达意思,尽管在可以表达的内容上失去了一些一般性,但在可以表达的内容上获得了内在的一致性。 前面提到的定义的一个例子是电子表格程序的单元格中的公式集——当移动到不同的列和行单元格时,不期望它们给出相同的含义,即单元格标识符被更改。单元格标识符是预期含义的一部分,而不是多余的。因此,对于一组公式中的单元格标识符,每个电子表格结果都是唯一的。在这种情况下,一致的模块化语义是使用单元格标识符作为单元格公式的纯函数的输入和输出(见下文)。 超文本标记语言,也就是HTML——用于静态网页的语言——是高度(但不是完美的__abc0)声明性语言的一个例子,(至少在HTML 5之前)没有能力表达动态行为。HTML可能是最容易学的语言。对于动态行为,命令式脚本语言(如JavaScript)通常与HTML结合使用。没有JavaScript的HTML符合声明性定义,因为每个标称类型(即标签)在语法规则的组合下保持其一致的含义。 声明性的一个竞争定义是语义语句的可交换的和幂等属性,即语句可以在不改变含义的情况下重新排序和复制。例如,为命名字段赋值的语句可以重新排序和复制,而不改变程序的含义,如果这些名称是模块化w.r.t.到任何隐含的顺序。名称有时意味着一个顺序,例如,单元格标识符包括它们的列和行位置——在电子表格上移动总数会改变其含义。否则,这些属性隐式地要求语义的全球一致性。通常不可能设计语句的语义,使它们在随机排序或重复的情况下保持一致,因为顺序和重复是语义固有的。例如,语句“Foo存在”(或构造)和“Foo不存在”(和破坏)。如果有人认为预期语义的随机不一致是普遍存在的,那么他就认为这个定义对于声明性属性来说是足够普遍的。本质上,这个定义作为一个广义的定义是空洞的,因为它试图使一致性与语义正交,也就是说,违抗语义的宇宙是动态无界的,不能在全球一致性范式中捕获的事实。 要求低层操作语义(结构求值顺序)具有交换性和幂等属性,将操作语义转换为声明性的本地化模块语义,例如纯函数式编程(包括递归而不是命令式循环)。这样,实现细节的操作顺序就不会影响(即将在全球范围内扩展到)高级语义的一致性。例如,电子表格公式的求值顺序(理论上也是复制)并不重要,因为直到所有输出都计算完毕后,才将输出复制到输入,即类似于纯函数。 C, Java, c++, c#, PHP和JavaScript不是特别声明性的。 Copute的语法和Python的语法更具有声明性耦合到 预期的结果,即一致的句法语义,消除无关的,所以可以很容易地 在他们忘记代码之后再理解它。Copute和Haskell强制执行 操作语义的确定性和鼓励“不要重复 你自己”(DRY),因为他们只允许纯函数范式 2即使我们可以证明一个程序的语义,例如使用Coq语言,这也仅限于在打字中表示的语义,并且键入永远不能捕获一个程序的所有语义——即使对于不是图灵完备的语言也不行,例如使用HTML+CSS,它可能表示不一致的组合,因此具有未定义的语义。 许多解释错误地声称只有命令式编程才有语法有序的语句。我澄清了这个命令式编程和函数式编程的混淆。例如,HTML语句的顺序并不会降低其含义的一致性。
声明式与命令式
声明性属性是奇怪的、迟钝的,并且很难在技术上精确的定义中捕获,这种定义仍然是通用的且不含糊的,因为它是一种天真的概念,即我们可以声明程序的意义(又称为语义)而不会引起意想不到的副作用。在表达意义和避免意想不到的效果之间有一种内在的紧张关系,这种紧张关系实际上来自编程和我们的宇宙的不完备定理。
将声明式定义为“< em > what 用< / em >”而将命令式定义为“< em >请;用< / em >”是过度简化、技术上不精确的,而且通常是模棱两可的。“什么”是输出编译器的程序中的“如何”,这是一种模棱两可的情况。
显然,无限递归,使语言图灵完备在语义上也是类似的——不仅仅是在求值的语法结构中(也就是操作语义)。这在逻辑上是一个类似于Gödel的定理-“任何完整的公理系统也是不一致的”的例子。想想这句话自相矛盾的诡异之处吧!它也是一个例子,说明了语义的表达式如何没有可证明的边界,因此我们无法证明__abc2一个程序(类似于它的语义)停止,也就是停止定理。
不完备性定理源于我们宇宙的基本性质,正如热力学第二定律所述,它是“熵(也就是独立可能性的#)趋势是永远的最大值吗”。程序的编码和设计永远不会结束——它是活的!——因为它试图解决现实世界的需求,而现实世界的语义总是在变化,并趋向于更多的可能性。人类从未停止发现新事物(包括程序中的错误;-)。
为了在这个没有边界的奇怪宇宙中精确地和技术地捕捉上述所期望的概念(想想吧!我们的宇宙没有“外部”),需要一个简洁但看似不简单的定义,除非深入解释,否则听起来是不正确的。
定义:
声明性属性是指只能存在一组可能的语句来表达每个特定的模块语义。 命令式属性__abc0是对偶,其中语义在组合下是不一致的,并且/或可以用语句集的变化来表示。
声明性属性是指只能存在一组可能的语句来表达每个特定的模块语义。
命令式属性__abc0是对偶,其中语义在组合下是不一致的,并且/或可以用语句集的变化来表示。
声明性的这个定义在语义范围内是独特的当地的,这意味着它要求模块化语义保持其一致的含义,而不管它在全球范围内实例化和使用的位置和方式。因此,每个声明性模块语义本质上应该与所有可能的其他语义正交——而不是一个不可能(由于不完备定理)的全球算法或模型来见证一致性,这也是罗伯特·哈珀(卡内基梅隆大学计算机科学教授,标准ML的设计师之一)的“更多并不总是更好”的观点。
这些模块化声明性语义的例子包括范畴理论函子,例如的Applicative、名义类型、命名空间、命名字段和w.r.t.,然后是纯函数编程的语义操作级。
Applicative
因此,设计良好的声明性语言可以更清楚地表达意思,尽管在可以表达的内容上失去了一些一般性,但在可以表达的内容上获得了内在的一致性。
前面提到的定义的一个例子是电子表格程序的单元格中的公式集——当移动到不同的列和行单元格时,不期望它们给出相同的含义,即单元格标识符被更改。单元格标识符是预期含义的一部分,而不是多余的。因此,对于一组公式中的单元格标识符,每个电子表格结果都是唯一的。在这种情况下,一致的模块化语义是使用单元格标识符作为单元格公式的纯函数的输入和输出(见下文)。
超文本标记语言,也就是HTML——用于静态网页的语言——是高度(但不是完美的__abc0)声明性语言的一个例子,(至少在HTML 5之前)没有能力表达动态行为。HTML可能是最容易学的语言。对于动态行为,命令式脚本语言(如JavaScript)通常与HTML结合使用。没有JavaScript的HTML符合声明性定义,因为每个标称类型(即标签)在语法规则的组合下保持其一致的含义。
声明性的一个竞争定义是语义语句的可交换的和幂等属性,即语句可以在不改变含义的情况下重新排序和复制。例如,为命名字段赋值的语句可以重新排序和复制,而不改变程序的含义,如果这些名称是模块化w.r.t.到任何隐含的顺序。名称有时意味着一个顺序,例如,单元格标识符包括它们的列和行位置——在电子表格上移动总数会改变其含义。否则,这些属性隐式地要求语义的全球一致性。通常不可能设计语句的语义,使它们在随机排序或重复的情况下保持一致,因为顺序和重复是语义固有的。例如,语句“Foo存在”(或构造)和“Foo不存在”(和破坏)。如果有人认为预期语义的随机不一致是普遍存在的,那么他就认为这个定义对于声明性属性来说是足够普遍的。本质上,这个定义作为一个广义的定义是空洞的,因为它试图使一致性与语义正交,也就是说,违抗语义的宇宙是动态无界的,不能在全球一致性范式中捕获的事实。
要求低层操作语义(结构求值顺序)具有交换性和幂等属性,将操作语义转换为声明性的本地化模块语义,例如纯函数式编程(包括递归而不是命令式循环)。这样,实现细节的操作顺序就不会影响(即将在全球范围内扩展到)高级语义的一致性。例如,电子表格公式的求值顺序(理论上也是复制)并不重要,因为直到所有输出都计算完毕后,才将输出复制到输入,即类似于纯函数。
2即使我们可以证明一个程序的语义,例如使用Coq语言,这也仅限于在打字中表示的语义,并且键入永远不能捕获一个程序的所有语义——即使对于不是图灵完备的语言也不行,例如使用HTML+CSS,它可能表示不一致的组合,因此具有未定义的语义。
许多解释错误地声称只有命令式编程才有语法有序的语句。我澄清了这个命令式编程和函数式编程的混淆。例如,HTML语句的顺序并不会降低其含义的一致性。
编辑:我在Robert Harper的博客上发布了下面的评论:
在函数式编程中…变量的变化范围就是类型 取决于如何区分函数式和命令式 编程时,你的“可赋值对象”在命令式程序中也可能有 对其可变性设置界限的类型 我目前欣赏的唯一不混淆的函数定义 编程是a)函数作为一类对象和类型;b) 递归优先于循环,和/或c)纯函数-即。 对象的期望语义不受影响的那些函数 程序记忆时(因此完全纯功能 编程并不存在于通用的表示性语义中 由于操作语义的影响,例如内存 配置< /一口> < / sub >)。< / p > 纯函数的幂等性是指函数调用 它的变量可以用它的值代替,这通常不是这样的 一个命令式程序的参数的情况。纯函数 似乎是对未组合状态转换的声明性W.R.T. .输入类型和结果类型之间 但纯函数的组合不保持任何这样的 一致性,因为有可能模拟副作用(全局的 在纯函数式编程语言中, 例如,Haskell的IOMonad,而且完全不可能 防止在任何图灵完备纯函数编程中这样做 语言。< / p > 因为我写了在2012年,这似乎是类似的共识 你最近的博客中的注释,说明声明式编程是一个 尝试捕获这样的概念,即预期的语义永远不会 不透明。不透明语义的例子有顺序依赖, 依赖于操作中高级语义的擦除 语义层(例如类型强制转换不是转换,也不是具象化的泛型 限制高级语义),以及对变量值的依赖 不能被编程语言检查(证明正确)。 因此,我得出结论,只有非图灵完备语言可以 声明性。< / p > 因此,声明性语言的一个明确而独特的属性 它的输出可以被证明服从一些可枚举的集合 生成规则。例如,对于任何特定的HTML程序(忽略 口译者的不同之处),而不是照本宣科 (即不是图灵完备的)则其输出可变性可以为 可列举的。或者更简洁地说,HTML程序是一个纯函数 它的可变性。同上,电子表格程序是其纯粹的功能 输入变量。< / p > 所以在我看来,声明性语言是语言的对立面 无限递归,即根据Gödel的第二个不完整性 定理自参照定理不能被证明 Lesie Lamport讲述了一个关于欧几里得的童话故事 解决了Gödel的不完备性定理,并将其应用于数学证明 在编程语言上下文中,类型和之间的一致性 逻辑(Curry-Howard通信等).
在函数式编程中…变量的变化范围就是类型
取决于如何区分函数式和命令式 编程时,你的“可赋值对象”在命令式程序中也可能有 对其可变性设置界限的类型 我目前欣赏的唯一不混淆的函数定义 编程是a)函数作为一类对象和类型;b) 递归优先于循环,和/或c)纯函数-即。 对象的期望语义不受影响的那些函数 程序记忆时(因此完全纯功能 编程并不存在于通用的表示性语义中 由于操作语义的影响,例如内存 配置< /一口> < / sub >)。< / p > 纯函数的幂等性是指函数调用 它的变量可以用它的值代替,这通常不是这样的 一个命令式程序的参数的情况。纯函数 似乎是对未组合状态转换的声明性W.R.T.
因为我写了在2012年,这似乎是类似的共识 你最近的博客中的注释,说明声明式编程是一个 尝试捕获这样的概念,即预期的语义永远不会 不透明。不透明语义的例子有顺序依赖, 依赖于操作中高级语义的擦除 语义层(例如类型强制转换不是转换,也不是具象化的泛型 限制高级语义),以及对变量值的依赖 不能被编程语言检查(证明正确)。
这里有一些关于所提到的“类型”的好答案。
我还提供了一些额外的、更“奇特”的概念,通常与函数式编程人群有关:
命令式/声明/功能方面在过去可以很好地分类泛型语言,但现在所有“大语言”(如Java、Python、Javascript等)都有一些选项(通常是框架)来表达其主要关注点(通常是命令式)之外的“其他关注点”,并表达并行进程、声明式函数、lambda等。
所以这个问题的一个很好的变体是“今天对框架进行分类的好方面是什么?” ... 一个重要的方面是我们可以标记“编程风格”…< / p >
这是一个很好的例子。正如你可以在维基百科上阅读jQuery,
jQuery的一系列核心特性——DOM元素选择、遍历和操作——由它的选择器引擎(…)支持,创建了一种新的“编程风格”,融合了算法和DOM数据结构
因此jQuery是关注“新的编程风格”的最佳(流行)示例,它不仅是面向对象的,而且是“融合 算法和数据结构”。jQuery有点像电子表格,但不是“面向单元格”,是“dom节点的”…在这个上下文中比较主风格:
不融合:在所有“大语言”中,在任何函数式/声明式/命令式表达式中,通常是数据和算法的“不融合”,除非通过一些面向对象,即融合 严格的代数结构的观点。
Some fusion:所有经典的融合策略,如今都有一些框架将其作为范例…dataflow, 事件驱动编程(或旧域特定语言为awk和XSLT)…就像使用现代电子表格编程一样,它们也是响应式编程风格的例子。
大融合:是“jQuery风格”…jQuery是一种领域特定的语言,专注于“融合算法和DOM-data-structures”。PS:其他“查询语言”,如XQuery, SQL (PL作为命令式表达式选项)也是数据-算法融合的例子,但它们是岛,没有与其他系统模块融合…< a href = " https://projects.spring。io/ Spring -framework/" rel="nofollow noreferrer">Spring,当使用__abc0 -variant和Specification子句时,是另一个很好的融合示例。
命令式编程:告诉“机器”如何做某事,结果你想发生的事情就会发生。
声明式编程:告诉“机器”你想要发生什么,然后让计算机想出如何去做。
function makeWidget(options) { const element = document.createElement('div'); element.style.backgroundColor = options.bgColor; element.style.width = options.width; element.style.height = options.height; element.textContent = options.txt; return element; }
function makeWidget(type, txt) { return new Element(type, txt); }
注:区别不在于简洁、复杂或抽象。如前所述,区别是如何 vs 什么。