泛函编程(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-12-31 12:31:33

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

Linux C编程与Shell编程在开发实用工具方面的相同点总结

原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任.http://dgd2010.blog.51cto.com/1539422/1712280 以whois包中的mkpasswd的源码mkpasswd.c文件为例,    两者都需要做的事情:         (1)在程序运行前需要满足其运行环境,包括声明需要使用哪些库哪些文件,例如C中的条件编译,Shell编程中的部分条件判断,如文件是否存在.是否可执行         (2)具备灵活性和可移

结对编程和面向对象编程有何异同?

问题描述 结对编程和面向对象编程有何异同? 结对编程和面向对象编程有何异同?面向对象编程是不是没有对象就学不好? 解决方案 结对是敏捷开发说的,一个人写程序,一个人做codeview面向对象是指,用oopl ooad等技术写程序 解决方案二: 结对是敏捷开发说的,一个人写程序,一个人做codereview面向对象是指,用oopl ooad等技术写程序 解决方案三: 其实都是结对编程,但前者一般是和同性结对,而和男/女朋友(异性)那就是面向对象咯.总的来说基佬们结对编程,而有异性亲热朋友的就面向对

编程思想与编程技法是相互影响的两件事,然而也是不可拆分的一回事儿!

编程思想与编程技法是相互影响的两件事,然而也是不可拆分的一回事儿! 太阳火神的美丽人生 (http://blog.csdn.net/opengl_es) 本文遵循"署名-非商业用途-保持一致"创作公用协议 转载请保留此句:太阳火神的美丽人生 -  本博客专注于 敏捷开发及移动和物联设备研究:iOS.Android.Html5.Arduino.pcDuino,否则,出自本博客的文章拒绝转载或再转载,谢谢合作. 良好的编程习惯坚持久了,会自动升华: 不好的邋遢习惯拖得过久,会困顿不堪! 编

《OpenACC并行程序设计:性能优化实践指南》一 第1章 从串行编程到并行编程

第1章 从串行编程到并行编程 Rob Farber TechEnablement.com CEO/创始人 本章主要向读者介绍OpenACC,演示如何使用OpenACC编写运行在多核CPU和类似GPU加速器上的可移植并行程序,并展示如何在CPU和GPU上编译和运行OpenACC程序. 阅读本章后,读者将会理解以下内容: 如何创建.编译和运行OpenACC应用程序. 高性能OpenACC编程的三个准则. 数据并行和任务并行编程的基本概念. 理解大O表示法和Amdahl定律. 竞争条件.原子操作,以及

[读后感]编程能力与编程年龄

又是一篇读后感, 你懂的,这篇附文的重量级别有多重, 尤其对于我们这样奔四的老程序员! 还有之前那篇<[读后感]一位39岁程序员的困惑:知道得越多编程越慢怎么办?>, 稍加实践,你会发现, 让自已少想一些,你也能很容易地快起来, 只不过,需要施加 强迫症 式的自我叮嘱, 因为,我们这个年龄的人, 如果不是被责任心充满着, 那八成可能会是个一事无成,连温饱都解决不了的闲人了! 其实,在程序员这个职业来说,确实刚积累到一定程度,思维可以自然迸出火花的年龄, 也确实就是这个年龄,才真正能从上往下式的

c-【求助】SOCKET编程,网络编程

问题描述 [求助]SOCKET编程,网络编程 本人自学SOCKET,跟着教程写了几行代码 但是接收(recv)那个循环里面总是出问题,具体情况是运行到第二次的时候程序就蹦了. 我的改进:后来经过一天的思考推敲我发现是有一中返回情况SOCKET_ERRO没有处理 于是家进入了 但还是错误,所以特来贵吧寻求帮助,(ps:我之前试过不用realloc扩大空间储存程序是ok的,但是用了realloc就出问题了,但是我觉得主要问题又不是在realloc哪里,可能是recv那里),求大神花上几分钟看看我的代

android编程之多线程编程实例分析_Android

本文实例讲述了android编程之多线程编程实现方法.分享给大家供大家参考.具体分析如下: 该功能与前面<android开发socket编程之udp发送实例分析>中一样,当按下键后,发送文本框中数据给PC.不同的是把发送数据的功能放在一个线程socket_send中. 一.环境: win7 + eclipse + ndk 二.代码: 主类test_socket.java package test.soket; //import com.test_button.R; import java.io

《面向对象的思考过程(原书第4版)》一1.3 过程式编程与面向对象编程

本节书摘来自华章出版社<面向对象的思考过程(原书第4版)>一书中的第1章,第1.3节,[美] 马特·魏斯费尔德(Matt Weisfeld) 著黄博文 译更多章节内容可以访问"华章计算机"公众号查看. 1.3 过程式编程与面向对象编程 在我们深入了解面向对象开发的优势之前,先考虑一个更基本的问题:究竟什么是对象?这既是一个复杂的问题,也是一个简单的问题.它复杂是因为学习任何一种软件开发方法论都非易事.它简单是因为人们已经在按对象的方式进行思考.例如,当你看到一个人,你会把他

选择Web编程还是WinForm编程? 用.net(c#)现在是都做,可是总感觉应该选择一个方向,到底是选择哪一样呢?

问题描述 选择Web编程还是WinForm编程?由于公司接的项目多,用.net(c#)现在是都做,可是总感觉应该选择一个方向,到底是选择哪一样呢?选择哪一个对将来的发展更有利???有点迷茫!!! 解决方案 解决方案二:哦,忘了说明一点了以前做的是WEB方面的,可到了公司后,有接触了winform方面,现在想确定一下,到底是往哪个方向走???解决方案三:对于未来不好确定,不过现在应该是做Web的多...解决方案四:web好像现在很热不过我是做winform的,呵呵解决方案五:很多人都说将来是网络的

android编程之多线程编程实例分析

本文实例讲述了android编程之多线程编程实现方法.分享给大家供大家参考.具体分析如下: 该功能与前面<android开发socket编程之udp发送实例分析>中一样,当按下键后,发送文本框中数据给PC.不同的是把发送数据的功能放在一个线程socket_send中. 一.环境: win7 + eclipse + ndk 二.代码: 主类test_socket.java package test.soket; //import com.test_button.R; import java.io