泛函编程(1)-泛函编程是如何实现的

泛函编程就是把函数组合起来形成一个完整的程序。可想而知,函数组合的过程可以是曲折的,形成的程序可以是复杂的。那么泛函编程又是如何保证一个复杂的函数组合程序是正确无误的呢?首先,泛函编程的函数组合(Functional Composition)遵循一定的数学定律(Mathematical Laws),这保证了组成的函数具备要求的行为特征(Behavior)。再者,所有组件函数都必须具备行为不可变化特性,即无论在任何场合,都不会因为产生了不同的最终结果而影响它们的行为。如果是这样,组合函数的行为都是可预知的,那么它们在程序中的作用也就可控了。这个什么不可变化特性解释的够绕的了吧?实际上这也是泛函编程的重点所在,我看还是要解释清楚才行。

泛函程序是由纯函数组成。所谓纯函数(Pure Function)是指这个函数的结果完全或只依赖它的输入。对于任何一个输入值只会产生一个唯一的相同结果,而不会因为什么其它的原因影响而变成另一个不同的结果。一个函数是由一个或多个表达式组成。组成一个纯函数的表达式都必须是可以“等量替换“的,意思是每个表达式都可以用这个表达式的结果替代而不会影响整个函数的行为结果。我抛开了英文Referencial Transparent的字面意思把它翻译成”可等量替换的“。我们可以通过”等量替换“方式来分析理解函数行为。纯函数(Pure Function)只依赖输入产生结果,不会造成任何”附带影响“(Side Effect)。所谓”附带影响“是指计算一个表达式后影响了函数的结果。因为泛函程序是由纯函数组成,纯函数是”可等量替换的“,具备行为不可变化特性,所以能保证泛函程序的正确性。

无“附带影响”、可“等量替换”作为泛函程序正确性的保障,或许在这里应该用一些实例来说明:

先来个超简单的例子:这个表达式 1+1=2够简单了吧。在Scala语言中 “+” 是个函数名称,我们可以确定这个“+”函数是个纯函数,因为我们可以放心的用结果2来“等量替代” 表达式1+1。

再来个比较像样的例子:

1 val x = "Hello, World"
2 x: java.lang.String = Hello, World
3 scala> val r1 = x.reverse
4 r1: String = dlroW ,olleH
5 scala> val r2 = x.reverse
6 r2: String = dlroW ,olleH

我们试着把 r1 和 r2 中的 x 用 x 的结果 "Hello, World"来替代:

1 val r1 = "Hello, World".reverse
2 r1: String = dlroW ,olleH
3 scala> val r2 = "Hello, World".reverse
4 r2: String = dlroW ,olleH

r1和r2的值没有改变。那么我们可以说x是可“等量替换“的。实际上r1和r2也都是可”等量替换“的,当它们出现在一些更大的程序中时我们同样可以运用”等量替换“而不改变程序的行为。

那么再来个反面教材:

1 val x = new StringBuilder("Hello")
2 x: java.lang.StringBuilder = Hello
3 scala> val y = x.append(", World")
4 y: java.lang.StringBuilder = Hello, World
5 scala> val r1 = y.toString
6 r1: java.lang.String = Hello, World
7 scala> val r2 = y.toString
8 r2: java.lang.String = Hello, World

当我们把 y 用它的表达式替代后:

1 val x = new StringBuilder("Hello")
2 x: java.lang.StringBuilder = Hello
3 scala> val r1 = x.append(", World").toString
4 r1: java.lang.String = Hello, World
5 scala> val r2 = x.append(", World").toString
6 r2: java.lang.String = Hello, World, World

显然,虽然r1和r2都等于y,但把y用它的结果x.append(", World")替换后r1 ≠ r2。这说明StringBuilder.append不是一个纯函数,我们决不能用它来进行函数组合(Function Composition),因为组成的程序行为是不可预料的。

从以上的例子中我们还可以得出结论:泛函程序能用正常的逻辑来理解,它的作用是可预测的,不容易出现粗心错误,可以放心使用。

再往深处想一下,上面例子StringBuilder.append之所以不是纯函数是因为StringBuilder是一个内容可以改变的数据结构(data structure),是"可改变的“(mutable)数据结构。泛函编程要求尽量使用”不可改变的“(Immutable)数据结构来保证程序的纯洁性。泛函编程就好像是使用”不可改变的“数据结构过程的挣扎,起码对我来说是这样的。

在这篇的结尾顺便示范一下泛函编程的风格:

下面这个例子是我们熟悉的OOP风格:

 1 def createErrorMessage(errorCode: Int) : String = {
 2    var result : String = _          //声明一个变量
 3    errorCode match {                //根据不同情况重新对变量result赋值
 4        result = "Network Failure"   // 对变量result赋值
 5      case 2 =>
 6        result = "I/O Failure"       // 对变量result赋值
 7      case _ =>
 8       result = "Unknown Error"      // 对变量result赋值
 9    }
10    return result;            // 返回结果
11 }

以上是典型的指令式编程(Imperative Programming);通过改变变量值来实现程序的状态转变。看看泛函编程例子:

1 def createErrorMessage(errorCode: Int) : String = errorCode match {
2           case 1 => "Network Failure"
3           case 2 => "I/O Failure"
4           case _ => "Unknown Error"
5 }

首先,没有中间变量。整个函数简洁明了的多。不经过中间变量直接返回结果;这就是泛函编程的一个风格特征。

时间: 2024-08-30 02:11:16

泛函编程(1)-泛函编程是如何实现的的相关文章

函数式编程与面向对象编程的比较

函数式编程作为结构化编程的一种,正在受到越来越多的重视.工程中不在只是面向对象编程,更多的人尝试着开始使用函数式编程来解决软件工程中遇到的问题. 什么是函数式编程?在维基百科中给出了详细的定义,函数式编程(英语:functional programming)或称函数程序设计,又称泛函编程,是一种编程范型,它将电脑运算视为数学上的函数计算,并且避免使用程序状态以及易变对象.函数编程语言最重要的基础是λ演算(lambda calculus).而且λ演算的函数可以接受函数当作输入(引数)和输出(传出值

物联网网络编程、Web编程综述

本文是基于嵌入式物联网研发工程师的视觉对网络编程和web编程进行阐述.对于专注J2EE后端服务开发的童鞋们来说,这篇文章可能稍显简单.但是网络编程和web编程对于绝大部分嵌入式物联网工程师来说是一块真空领域. 的确,物联网研发应该以团队协作分工的方式进行,所以有嵌入式设备端.网关.web前端.APP.后端开发等专属岗位.作为系统架构师,自然需要掌握各种岗位的关键技术.作为嵌入式工程师,掌握网络编程.web编程,能够极大地拓展自己的视野和架构思维,能够主动地对系统的各种协议和应用场景提出优化的见解

Python编程和 Lua编程的比较

Python编程和 Lua编程的比较 2016.4.21 定义函数: python: def functionname( parameters ): "函数_文档字符串" function_suite return [expression] lua: --[[ function returning the max between two numbers --]] function max(num1, num2) if (num1 > num2) then result = num

python_way.day7 模块(configparser,xml,shutil,subprocess)、面向对象(上)(创建类,类的构成,函数式编程与面向对象编程的选择,类的继承)

python_way.day7 1.模块 configparser,xml,shutil,subprocess 2.面向对象(上) 创建类,类的构成,函数式编程与面向对象编程的选择,类的继承 1.模块 configparser 用于处理特定格式的文件,其本职上使用open来操作,只能是  [test1] 特定的格式 [test1] k1 = 123 k2 = True [test2] k1 = 123 k2 = v1 文件内容 1.获取 import configparser #打开文件找到文件

XMPP-05Socket编程之网络编程篇

要学习XMPP,就要先了解Socket编程,在学习Socket之前,还要先了解一下网络编程 一.网络编程基本概念 通过使用套接字来达到进程间通信目的的编程就是网络编程. 网络编程从大的方面说就是对信息的发送到接收,中间传输为物理线路的作用,编程人员可以不用考虑…… 网络编程最主要的工作就是在发送端把信息通过规定好的协议进行组装包,在接收端按照规定好的协议把包进行解析,从而提取出对应的信息,达到通信的目的!中间最主要的就是数据包的组装,数据包的过滤,数据包的捕获,数据包的分析,当然最后再做一些处理

Atitit 函数式编程与命令式编程的区别attilax总结  qbf

Atitit 函数式编程与命令式编程的区别attilax总结  qbf 1.1. 函数式程序就是一个表达式.命令式程序就是一个冯诺依曼机的指令序列. 命令式编程是面向计算机硬件的抽象,有变量(对应着存储单元),赋值语句(获取,存储指令),表达式(内存引用和算术运算)和控制语句(跳转指令),一句话,命令式程序就是一个冯诺依曼机的指令序列. 而函数式编程是面向数学的抽象,将计算描述为一种表达式求值,一句话,函数式程序就是一个表达式. 1.2. 面向对象语言中,数据类型分为两种--基本类型和对象类型(

全新编程模式---站立编程--独创编程方式,可能会流行

多年来我们程序员已经习惯了编程模式--坐着编程 一来到编程办公室,我们马上坐下,打开空调,松软的旋转座椅,坐下来就可以编程.于是人们只知道世界上只有这种编程模式--坐着编程. 这个模式不是不好,可是如果坚持只用这个编程模式,会带来严重问题,坐久了屁股痛,最后没办法坐了.于是只能躺着编程. 坐着编程,身体缺少运动,每况愈下,身体不堪负重,垮了.生成各种疾病,最后竟然有的程序员英年死去,没办法编程了! 我编程十几年,曾经也是运动健将,但是因为坐着编程,身体也不行了,坐下没多久屁股就痛了.现在是左右为

基于对象编程与面向对象编程(表达式计算器3)

基于对象编程与面向对象编程 我们的最终目的是用C++设计一个面向对象的表达式计算器,所以非常有必要弄清楚,什么是基于对象编程和面向对象的编程.而要弄清楚这一点,又要先弄明白什么是值语言,什么是对象语义 值语义:对象的拷贝与原对象无关,拷贝后与原对象脱离关系,互不影响.这种拷贝叫深拷贝.拷贝之后脱离关系,只要在拷贝的时候都为对象分配内存空间就行了.某种些情况下算是一种对资源的浪费 值语义例子 class Test { private: int * pNum_; public: Test(int n

[从玩游戏来理解编程]关于面向对象编程的浅解(1)

我最近在玩星际争霸2,感觉到面向对象真是太重要了.有三个种族,每个种族有自己的兵种,一个兵种就是一个类,而且他们的父类都是一个类. 每个单位都有自己的属性和技能,之前学习的c语言是面向过程的,慢慢的也理解到了面向对象在某些地方的重要性. 之前都是在学习算法,实现上用的c语言,看不出来到底有什么区别,反而在用c来的更简洁和更快,最近慢慢的接触到小项目之类的什么的就慢慢感觉到面向对象的重要性. 最近在看一本书叫<making games with python and pygame>,这本书很好,

我是如何开始去了解Python函数式编程--Python函数式编程初涉

Python函数式编程 1. 开始我们会了解什么是函数式编程: 函数:function 函数式:functional,一种编程范式 函数式编程特点:把计算视为函数而非指令,贴近计算 纯函数式编程:不需要变量,没有副作用,测试简单,支持高阶函数,代码简洁 Python支持的函数式编程特点: 不是纯函数式编程:允许有变量 支持高阶函数:函数也可以作为变量传入 支持闭包:有了闭包就能返回函数 有限度的支持匿名函数 2. 高阶函数 变量可以指向函数,函数名其实就是指向函数的变量,而高阶函数其实就是可以接