函数式编程与声明式语言

article/2025/10/25 21:55:19

原文链接:https://www.cnblogs.com/doit8791/p/8232821.html

编程语言可以分成两类:

  • 命令式
  • 声明式

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

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

声明式编程的发源

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

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

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

阶乘的三种编程实现

C(命令式)——

1int factorial(int n)
2{
3    int f = 1;
4    for (; n > 0; --n) f *= n;
5    return f;
6}

Lisp(函数式)——

1(defun factorial(n)
2  (if (= n 0)
3    1                               //  若n等于0,则n!等于1
4    (* n (factorial(- n 1)))))      //  否则n!等于n* (n-1)

Prolog(逻辑式)——

1// 0! 等于1
2factorial(0,1).
3// 若M等于N-1且 M!等于Fm且F等于N*Fm,则N! 等于F
4factorial(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上:

1int factorial(int n)
2{
3    return n == 0 ? 1 : n * factorial(n - 1);
4}

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

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

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

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

绘成表格如下:

范式程序输入输出程序设计程序运行
命令式自动机初始状态最终状态设计指令命令执行
函数式数学函数自变量因变量设计函数表达式变换
逻辑式逻辑证明题设结论设计命题逻辑推理

 

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

  • 命令式编程通过一系列改变程序状态的指令来完成计算,声明式编程只描述程序应该完成的任务。命令式编程模拟电脑运算,是行动导向的,关键在于定义解法,即“怎么做”,因而算法是显性而目标是隐性的;声明式编程模拟人脑思维,是目标驱动的,关键在于描述问题,即“做什么”,因而目标是显性而算法是隐性的。
  • 函数式编程通过数学函数的表达式变换和计算来求值。
  • 逻辑式编程通过一系列事实和规则,利用数理逻辑来推导或论证结论。
  • 命令式编程中的变量代表抽象化的内存,所存内容可能改变。声明式编程中的变量代表抽象化的符号,所指对象一般不会改变。
  • 声明式编程专注问题的分析和表达而不是算法实现,不用指明执行顺序,一般没有或极少副作用,也不存在内存管理问题。这些都大大降低了编程的复杂度,同时也非常适合于并发式计算。
  • 编程语言的流行程度与其擅长的领域密切相关。函数式语言和逻辑式语言擅长基于数理逻辑的应用,命令式语言擅长基于业务逻辑的、尤其是交互式或事件驱动型的应用。
  • 声明式语言与命令式语言之间并无绝对的界限,它们均建立于低级语言之上,并且互相渗透融合。
  • 在命令式语言中引入函数或过程,是一种向声明式风格的趋近。
  • 编程是寻求一种机制,将指定的输入转化为指定的输出。
  • 三种核心编程范式采用如下不同的机制:
    • 命令式:自动机机制,通过设计指令完成从初始态到最终态的转变。
    • 函数式:数学变换机制,通过设计函数完成从自变量到因变量的计算。
    • 逻辑式:逻辑证明机制,通过逻辑推理完成从题设到结论的证明。 

http://chatgpt.dhexx.cn/article/06e6ZQ1y.shtml

相关文章

深入理解函数式编程(上)

总第539篇 2022年 第056篇 函数式编程是一种历史悠久的编程范式。作为演算法,它的历史可以追溯到现代计算机诞生之前的λ演算,本文希望带大家快速了解函数式编程的历史、基础技术、重要特性和实践法则。 在内容层面,主要使用JavaScript语言来…

【函数式编程】什么是函数式编程? C语言为何不是函数式语言?

什么是函数式编程? C语言为何不是函数式语言? 函数式语言有两个主要的特点:1. 函数是“头等公民”。2. 数据的“immutability”(不变;永恒性;不变性;). 操作的“无副作用”, 这规避…

MP2451 VOUT计算公式 表

MP2451 DC-DC管理芯片 手册就不贴出来了. 下面贴出需要用的电阻值和需要转换的电压

MP2451电路调试笔记

MP2451电路调试笔记 EN脚内部有一个8V的稳压管,因此电路中R5可不要,R2取124K接到VIN脚。C7取100nF ,C8要根据不同输出电压选取,3.3V输出时取值33pFC7、C8取值不对,纹波会很大

【MSP430单片机】MSP430G2553程序,MSP430G2553单片机教程,MSP430G2553实战演练

文章目录 开发环境板子介绍官网示例代码下载MSP430普通IO口控制IO口外部中断MSP430时钟系统MSP430不精确延时之delay_msMSP430定时器_CCR0溢出中断MSP430定时器_定时器计数溢出中断MSP430定时器_PWM波形产生MSP430串口_收发9600波特率115200 波特率 MSP430ADCMSP430 Flash读写 …

LPC4357JET256/LPC4337FET256/LPC4337JET256 32位MCU 204MHz 1MB

【详情】LPC4300系列微控制器(MCU)拥有全世界首款非对称双核数字信号控制器体系结构,配有ARM Cortex-M4和Cortex-M0处理器。这些NXP Cortex-M4 MCU配有Cortex-M0协处理器,优势在于,可在单一体系结构、开发环境中,开发数字信号处理…

STM32MP157驱动开发——Linux I2C驱动

相关文章:正点原子教程第四十章——Linux I2C驱动实验 0.前言 为了简化笔记的编写以及降低工作量,本节开始相关的基础知识部分通过引入原子哥的教材链接来完成,有兴趣的可以进入学习。   上一节学完 RGB LCD 本来想直接学习 RGB 转 HDMI 实…

SPI转can芯片mcp2515

开发环境 CPU:RK3399 ARCH: aarch64 KERNEL:Linux4.4 OS:ubuntu18.04 mcp2515芯片相关信息 CAN、SPI接口控制电路图 修改设备树文件 文件路径:kernel/arm64/boot/dts/rockchip/rk3399.dts 增加spi节点,具体增加那…

MSP432 TFTLCD ILI9481 软件SPI

下面是我用的LCD屏的图片 CS : PF1 RS : PF2 RST: PF3 MOSI: PL4 SCK: PL5 代码: SPI.h #ifndef _SPI_…

MPC5744P-SPI

1.结构 5744的SPI模块支持全双工三线同步传输,可运行在主机或从机模式,分别含有深度为5的FIFO发送和接收缓存区。其结构如下图。SPI配置允许模块发送和接收串行数据,同时也支持带FIFO缓存区的的进行扩展队列操作的数据传输。模块接收和发送的…

SPI协议、MCP2515裸机驱动详解----主流SPI总线接口原理

最近看到一个介绍SPI接口原理的帖子,看完觉得甚好。特来分析给大家一起学习。 SPI概述 Serial Peripheral interface 通用串行外围设备接口 是Motorola首先在其MC68HCXX系列处理器上定义的。SPI接口主要应用在 EEPROM,FLASH,实时时钟&#…

MP2451 使能脚电阻判断

MP2451使能脚(EN)内部接一个稳压管,防止EN所接电压过大。 EN输入的电压范围:从PDF文档中找到EN的开启电压和最高电压。 Enable up Threshold 1.4-1.7 因为计算时需要考虑其极端现象,所以开启电压的临界电压选择最高,1.7v。最高…

CSM3416SF兼容MP2451,MCP16301HT-E,LT1933ES6,AOZ1282CI

CSM3416SF是一颗高耐压DC-DC降压芯片,宽电压范围输入,完美兼容MP2451,MCP16301HT-E,LT1933ES6,AOZ1282CI,助力智能电表市场,赋能车库门驱动系统。

S32K144开发笔记5 - SPI驱动MCP2515

1、接线图 PTB13 — INT 接收数据中断引脚 PTB14 — CLK 时钟 PTB15 — MISO 接收 PTB16 — MOSI 发送 PTB17 — CSN 片选 2、软件SPI 2.1、GPIO口配置 鼠标放在如下位置右击选择Pin Functional Properities,进入引脚属性配置 PTB13引脚配置如下: PT…

nRF52笔记(26)QSPI接口液晶显示屏

1 平台条件 硬件:nrf52840 软件:sdk17.0 2 QSPI概述 QSPI 外设支持使用 SPI 与外部闪存设备进行通信 此处列出了 QSPI 外设的主要特性: • 单/双/四通道 SPI 输入/输出 • 2–32 MHz 可配置时钟频率 • 从/到外部闪存的单字读/写访问 • …

MPC5744-LINFlexD

目录 一、基本介绍1.功能2.时钟源3.外设控制器4.中断向量5.基地址 二、寄存器介绍1.LIN控制寄存器1 LINFlexD_LINCR12.LIN中断使能寄存器LINFlexD_LINIER3.LIN状态寄存器LINFlexD_LINSR4.LIN错误状态寄存器LINFlexD_LINESR5.UART控制寄存器LINFlexD_UARTCR6.UART状态寄存器LINF…

MP2456的芯片的学习

本章将讲述MP2456的特征、性能、参数、应用电路、以及使用时的注意事项。小白总结,如有错误,请大神指教。 目录 一、MP2456的特征 二、MP2456的性能和参数 四、MP2456使用时的注意事项 五、名词解释 一、MP2456的特征 (1)MP…

硬件电路-MP2451组成的电压反转/极性反转电路设计

板上要集成一个18V供电的模拟信号处理电路,包括线圈驱动、小信号拾取、滤波、二级放大等部分。因此,需要板上提供18V电源。正负电压需要分开控制,因为正电压需要兼作485传感器供电,此时关闭负电压部分节省耗电以及保护模拟端。 b…

MP2451的应用电路

电阻R32和电阻R23是怎么实现的电压,FB口的输出是0.8V。 正确的计算应该是: 0.8/10*(1027)这样就是输出的电压。 SS14是大电流二极管,可以用5819完全替代。还有续流二极管。 SS14是40V的耐压,SS12是20V的耐压。 8050可以替代BC8…

MP2451问题记录

数据手册 https://pan.baidu.com/s/1ggJs0y3 MP2451应用电路如下图 在我自己的应用电路中R1120K,R224k 问题一、 电源输出0.42V V(FB)手册中应等于0.8V,但在测试中发现该引脚电压仅为0.07V 0.07 * (12024) / 24 0.42V 判断是芯片损坏 问题二、FB引脚输…