声明式编程范式初探

news/2024/7/20 13:36:17 标签: 内存管理, java, 数据库

声明式编程范式初探

语言编程语言可以分成两类:

  • 命令式
  • 声明式

事实上,凡是非命令式的编程都可归为声明式编程。因此,命令式、函数式和逻辑式是最核心的三种范式。为清楚起见,我们用一幅图来表示它们之间的关系。

与命令式编程相对的声明式编程(declarative programming)。顾名思义,声明式编程由若干规范(specification)的声明组成的,即一系列陈述句:‘已知这,求解那’,强调‘做什么’而非‘怎么做’。声明式编程是人脑思维方式的抽象,即利用数理逻辑或既定规范对已知条件进行推理或运算

声明式编程的发源

声明式编程发轫于人工智能的研究,主要包括函数式编程(functional programming,简称FP)和逻辑式编程(logic programming,简称LP)。其中,函数式编程将计算描述为数学函数的求值,而逻辑式编程通过提供一系列事实和规则来推导或论证结论。

其实支持它们的语言出现得并不比命令式的晚多少——最早的函数式语言Lisp(LISt Processor)已有半个世纪的历史,最早之一的逻辑式语言Prolog(PROgramming in LOGic)也与C同龄。只是由于大多数更多地用于学术研究而非商业应用,颇有些‘养在深闺人未识’的味道。

起源的不同决定了这两大类范式代表着迥然不同的编程理念和风格:命令式编程是行动导向(Action-Oriented)的,因而算法是显性而目标是隐性的;声明式编程是目标驱动(Goal-Driven)的,因而目标是显性而算法是隐性的。为便于说明,我们分别用三种代表性的语言来实现阶乘(factorial)运算。

阶乘的三种编程实现

C(命令式)——

4

    for(; n > 0; --n) f *= n;

Lisp(函数式)——

4

    (* n (factorial(- n 1)))))     

Prolog(逻辑式)——

4

factorial(N,F) :-   M is N-1, factorial(M,Fm), F is N * Fm.

以上三段代码区别在哪里?C明确给出了阶乘的迭代算法,而Lisp仅描述了阶乘的递归定义,Prolog则陈述了两个关于阶乘的断言。

声明式编程的本质

我们最早接触的变量是代数方程中的x、y、z等,本质上是抽象化的符号,变量值是该符号在给定约束条件下的允许值。而命令式编程中的变量本质上是抽象化的内存,变量值是该内存的储存内容。通俗地说,前者好比姓名,所指之人是固定的;后者好比住址,所住之人是变化的。此外,等号在代数中是一种约束,而在许多命令式语言中则表示赋值。因此 i = i + 1 可以在命令式编程中出现,但绝不可能在数学推理中出现 —— 除非在反证法中。

声明式编程让我们重回数学思维:函数式编程类似代数中的表达式变换和计算,逻辑式编程则类似数理逻辑推理。其中的变量也如数学中的一样,是抽象符号而非内存地址,因此没有赋值运算,不会产生变量被改写的副作用(side-effect),也不存在内存分配和释放的问题。这既简化了代码,也减少了调试——不妨想一想,有多少bug是由于某个变量被意外改写或内存管理不慎而造成的?

声明式语言与命令式语言的相通之处

  • 首先,所有高级语言都建立于低级语言之上,最终转化为机器语言,声明式语言也不例外。
  • 其次,声明式语言与命令式语言并非泾渭分明,而是互相交叉渗透的。一些‘非纯粹’ 的声明式语言也提供变量赋值和流程控制,而一些命令式语言也在逐渐发展,通过利用其他程序或增加新的语言特征来实现声明式编程。

总的说来,在命令式语言中融入声明式的元素应当是一种趋势。尤其是函数式,它的一些特征已经在许多命令式语言中得到了支持。比较而言,声明式编程重目标、轻过程,专注问题的分析和表达而不致陷入算法的迷宫,其代码也更加简洁清晰、易于修改和维护。从这种意义上说,声明式语言天然地就比命令式语言更高级。

值得指出的是,声明式编程并不仅仅局限于函数式和逻辑式。比方说,C#中的attribute、Java中的annotation和XDoclet库等采用的也是具有声明式特征的属性导向式编程(Attribute-Oriented Programming,简称@OP)。再比如,Prograph、SISAL等数据流语言(dataflow language)采用的数据流式编程(Dataflow Programming)与函数式编程有不少共同点,同样属于声明式的范畴。还有一些语言如Oz、CHIP等支持与逻辑式编程相交的约束式编程(Constraint Programming)。此外,大家熟悉的数据库语言SQL,样式语言XSLT、CSS,标记语言HTML、XML、SVG,规范语言IDL(Interface Description Language)等等都是声明式的。算上它们,声明式语言所占的比例也是非常可观的。此前之所以没有提及,一方面,不少声明式语言采用的范式并没有专门的名称;另一方面,这些语言大多是领域特定语言,并且不少并非图灵完备的,有的连运算都没有。毕竟,目前我们的重点还是放在通用编程语言上。

其实用Lisp实现阶乘的方法也可以用在C上:

3

    returnn == 0 ? 1 : n * factorial(n - 1);

这是C的递归实现。除了细微的语法差别外,二者的确很相似,这说明用命令式语言也可以讲出声明式的味道。实际上,命令式语言提倡迭代而不鼓励递归,早期的Fortran 甚至都不支持递归。一则迭代比递归更符合命令式的思维模式,因为前者贴近机器语言而后者贴近数学语言;二则除尾递归(tail recursion)外,一般递归比迭代的开销(overhead)大。相反,声明式语言提倡递归而不支持迭代。就语法而言,它不允许迭代中的循环变量;就视角而言,迭代着眼微观过程而递归着眼宏观规律。

具体可以看看这个:漫谈递归

归根结底,编程是寻求一种机制,将指定的输入转化为指定的输出。三种范式对此提供了截然不同的解决方案:

  • 命令式把程序看作一个自动机,输入是初始状态,输出是最终状态,编程就是设计一系列指令,通过自动机执行以完成状态转变;
  • 函数式把程序看作一个数学函数,输入是自变量,输出是因变量,编程就是设计一系列函数,通过表达式变换以完成计算;
  • 逻辑式把程序看作一个逻辑证明,输入是题设,输出是结论,编程就是设计一系列命题,通过逻辑推理以完成证明。

绘成表格如下:

 

 

 

 

 

 

命令式

自动机

初始状态

最终状态

设计指令

命令执行

函数式

数学函数

自变量

因变量

设计函数

表达式变换

逻辑式

逻辑证明

题设

结论

设计命题

逻辑推理

短短一篇文章无法深入讲述,后面将展开,让大家看清楚声明式编程范式的全貌。

 

http://www.nowamagic.net/academy/detail/1220528


http://www.niftyadmin.cn/n/703910.html

相关文章

SpringBoot+Mybatis多数据源实践

目录 一、前言 二、多数据源事务 方案一 方案二 一、前言 随着业务量增大,数据库性能终究会遇到瓶颈,因此需要将部分业务进行分库处理,同时部分查询需要切换到性能更好的数据库,如阿里云的数仓,因此便会遇到多数据…

Hystrix参数配置

1、Hystrix参数配置文档 2、Hystrix参数配置示例 import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework…

redis+lua实现滑动窗口限流

前言 关于滑动时间窗口算法以及更多限流实践请走:接口限流实践 脚本 主要是利用redis的有序集合(zset)来实现,计算前一秒内已访问的次数主要使用zcount命令来计算在当前集合中指定区间分数内的成员数,如果不使用lua…

Python的设计哲学

2019独角兽企业重金招聘Python工程师标准>>> python >>> import thisBeautiful is better than ugly. # 优美胜于丑陋(Python以编写优美的代码为目标)Explicit is better than implicit. # 明了胜于晦涩(优美的代码应当…

PostgreSQL常用SQL

目录 授权 查看重复索引 显示每张表上的索引 数据库中单个表的大小(不包含索引) 查出所有表(包含索引)并排序 最耗IO SQL,单次调用最耗IO SQL TOP 5 总最耗IO SQL TOP 5 最耗时 SQL,单次调用最耗时…

Swoole和Swoft的那些事 (Http/Rpc服务篇)

https://www.jianshu.com/p/4c0f625d5e11 Swoft在PHPer圈中是一个门槛较高的Web框架,不仅仅由于框架本身带来了很多新概念和前沿的设计,还在于Swoft是一个基于Swoole的框架。Swoole在PHPer圈内学习成本最高的工具没有之一,虽然Swoft的出现降低…

Liquibase的基本使用说明

目录 介绍 使用 本地执行changeLog Jenkins执行步骤 第一次执行完成后目标数据库会多出两张表: 常见错误: 1: 修改了历史的脚本文件,执行会报下列错误 2: 如果是一直执行不完,有可能是数据库异常liquibaselock锁住了。 p…

Vue的第一个组件设计

我记得当时我拿起CakePHP,我很喜欢开始使用它是多么容易。这些文档不仅结构合理,详尽无遗,而且用户友好。多年以后,这正是我在Vue.js中感受到的。然而,与Cake相比,Vue文档仍然缺少一件事:一个真…