浅谈函数式编程

函数式编程(Functional Programming)是一种编程风格,它是相对于指令式编程风格而言的,常见的面向对象编程就是指令式编程风格。

指令式编程是面向计算机硬件的抽象,有变量(对应着存储单元),赋值语句(获取、存储指令),表达式(内存引用和算术运算)和控制语句(跳转语句)。

而函数式编程是面向数学的抽象,将计算描述为一种表达式求值。这里的函数实际就是数学中的函数,即自变量到因变量的映射。也就是说,一个函数的值仅决定于函数参数的值,不依赖其他状态。

函数式编程是一种抽象程度很高的编程范式,纯粹的函数式编程语言编写的函数没有变量,因此,任意一个函数,只要输入是确定的,输出就是确定的,这种纯函数我们称之为没有副作用。而允许使用变量的程序设计语言,由于函数内部的变量状态不确定,同样的输入,可能得到不同的输出,因此,这种函数是有副作用的。

在函数式语言当中,函数作为一等公民,可以在任何地方定义,在函数内或函数外,可以作为函数的参数或返回值,可以对函数进行组合,也可以将函数赋值给变量。严格意义上的函数式编程意味着不适用可变的变量,赋值,循环和其他命令式控制结构进行编程。

函数式编程风格带来的好处是:

  1. 函数式编程使用不可变对象作为变量,不会修改变量的值,而是返回一个新的值,如此这样,更容易理清头绪,使得单元测试和调试更加容易;
  2. 可以很自由地传递不可变对象,但对于可变对象来说,传递给其他代码之前,需要先建造一个以防万一的副本;
  3. 一旦不可变对象完成构造以后,就不会有线程因为并发访问而破坏对象内部状态,因为根本没有线程可以改变不可变对象的状态;
  4. 不可变对象让哈希表键值更安全,所以哈希表键要求必须是不可变对象,否则使用可变对象,如果对象状态发生变化,那么在哈希表中就找不到这个对象了;

具体到编程语言,Scala(静态语言)和Python(动态语言)都能比较的支持函数式编程风格,但是它们都不是纯函数式的,也就是说它们同时支持指令式风格和函数式风格。而Java基本是指令式风格,但自从Java8引入lambda表达式以后也开始部分支持函数式风格。函数式编程最典型的是诸如map, flatMap, reduce, filter等函数,它们的特点是支持某个函数作为上面这些函数的参数。

下面分别以Java、Scala和Python举例函数式编程,其中Java对函数式编程只是间接的支持(通过函数式接口),支持度比较有限,而Scala和Python就对函数式编程支持的比较好。

Java函数式编程举例:

1 package lxy.java.fp; 2  3 import java.util.*; 4 import java.util.function.*; 5  6  7 public class FPDemo { 8     //定义泛型方法,用以根据第二个参数指定的条件从第一个参数指定的集合中过滤部分元素,并返回过滤后的结果。这里的第二个参数是一个函数式接口。 9     public static <T> List <T> filter(List <T> list, Predicate <T> p) {10         List <T> results = new ArrayList <>();11         for (T s : list) {12             if (p.test(s)) {13                 results.add(s);14             }15         }16         return results;17     }18 19     public static void main(String[] args) {20         List <String> myList = Arrays.asList("Hello", "Java", "Python", "Scala");21 22         //通过匿名类的方式23         List <String> results = filter(myList, new Predicate <String>() {24             public boolean test(String t) {25                 return t.length() >= 5;26             }27         });28         System.out.println("through anonymous class:");29         System.out.println("strings with length more than 5:");30         for (String result : results) {31             System.out.println(result);32         }33 34         //行为参数化,通过匿名函数(即lambda表达式)方式,35         System.out.println("through lambda expression:");36         System.out.println("strings with length more than 5:");37         List <String> results2 = filter(myList, s -> s.length() >= 5);38         results2.forEach(s -> System.out.println(s));39 40         //很容易地将过滤条件由字符串长度大于等于5改为字符串以字母a结尾,41         // 这就是行为参数化,即将具体的逻辑(即行为或者函数)参数化,使得filter函数更加抽象,提高了代码复用度42         //否则需要写2个filter函数,一个过滤出长度大于等于5的字符串,另一个过滤出以字符a结尾的字符串43         System.out.println("strings ends with character 'a'");44         List <String> results3 = filter(myList, s -> s.endsWith("a"));45         results3.forEach(System.out::println);46 47         //Java流中的filter函数和map函数,注意这里的filter是Java库函数,和前面自定义的filter函数不一样48         System.out.println("strings with length more than 5 and its length:");49         myList.parallelStream().filter(s -> s.length() >= 5).map(s -> "(" + s + ", " + s.length() + ")")50                 .forEach(System.out::println);51 52         //高阶函数53         Function <Integer, Integer> f = (Integer x) -> x + 1;54         Function <Integer, Integer> g = (Integer x) -> x * x;55         Function <Integer, Integer> h = f.andThen(g);56         Function <Integer, Integer> r = f.compose(g);57 58         System.out.println("higher-order function:");59         System.out.println("h(2)=g(f(2))=" + h.apply(2));60         System.out.println("r(2)=f(g(2))=" + r.apply(2));61 62     }63 }

Scala函数式编程举例:

1 package lxy.scala.fp 2  3 object FPDemo { 4     //定义泛型方法,用以根据第二个参数指定的条件从第一个参数指定的列表中过滤部分元素,并返回过滤后的结果,结果类型仍然是List。 5     //这里的第二个参数是一个函数, 该函数输入参数类型为T,返回值类型为Boolean 6     def filter[T](list: List[T], f: T => Boolean) = 7         for {e <- list if f(e) == true} yield e 8  9     //高阶函数,该函数定义为g(f(x)),其中函数f和g都是作为参数在调用该高阶函数时指定的10     def highOrderFunction1(x: Int, f: Int => Int, g: Int => Int) = g(f(x))11 12     //定义嵌套函数,针对每个参数,外层函数都会返回一个函数,即内层函数13     //这里factor是自由变量,number是绑定变量。14     //闭包是一个函数,返回值依赖于声明在函数外部的一个或多个变量15     //函数multiplier返回的函数就是闭包,factor就是外部的变量,也叫自由变量,number是绑定变量(形式参数)16     def multiplier(factor: Int): Int => Int = {17         def multiplyByFactor(number: Int) = factor * number18 19         return multiplyByFactor20     }21 22     //柯里化函数,类型是(Int)(Int) => Int23     def multiplier2(factor: Int)(number: Int) = factor * number24 25     //这个函数实际跟multiplier效果是一样的,每传入一个参数factor,都会返回一个函数26     //闭包是一个函数,返回值依赖于声明在函数外部的一个或多个变量27     //函数multiplier3返回的函数就是闭包,factor就是外部的变量,也叫自由变量,number是绑定变量(形式参数)28     def multiplier3(factor: Int) = multiplier2(factor) _29 30     def main(args: Array[String]): Unit = {31         val myList = List("Hello", "Java", "Python", "Scala")32         println("strings with length more than 5")33         filter(myList, (s: String) => s.length >= 5).foreach(s => println(s))34 35         println("strings ends with character 'a'")36         filter(myList, (s: String) => s.endsWith("a")).foreach(println)37 38         println("strings with length more than 5 and its length:")39         //这里的filter不是自定义函数filter,而是库函数,返回长度大于等于5的字符串以及对应的长度40         myList.filter(_.length >= 5).map(s => (s, s.length)).foreach(println)41 42 43         val result = highOrderFunction1(2, (x: Int) => x + 1, (x: Int) => x * x)44         println("f(x)=x+1, g(x)=x*x, g(f(2))=" + result)45 46 47         println("multiplier(2)=" + multiplier(2))48         println("multiplier(2)(3)=" + multiplier(2)(3))49         val double = multiplier(2)50         println("double(3)=" + double(3))51 52         println("multiplier3(3)=" + multiplier3(3))53         println("multiplier3(3)(4)=" + multiplier3(3)(4))54         val triple = multiplier3(3)55         println("triple(4)=" + triple(4))56 57     }58 }

Python函数式编程举例:

1 #!/usr/bin/env python3 2 # -*- coding: utf-8 -*- 3  4 if __name__ == "__main__": 5     """ 6         Usage: ./fp_demo.py 7     """ 8  9     # 定义函数filter,第一个参数是列表,第二个参数是函数10     def filter(list, f):11         return [e for e in list if f(e) == True]12 13     # python中没有foreach函数,自己定义一个,其中第一个参数是函数,第二个参数是迭代器(列表)14     def foreach(f, iterator):15         for item in iterator:16             f(item)17 18     myList = ["Hello", "Java", "Python", "Scala"]19     resultList = filter(myList, lambda e: len(e) >= 5)20     print("strings with length more than 5", sep="\n")21     foreach(lambda e: print(e, sep="\n"), resultList)22 23     resultList2 = filter(myList, lambda e: e.endswith("a"))24     print("strings ends with character 'a'", sep="\n")25     foreach(lambda e: print(e, sep="\n"), resultList2)26 27     # 这里的map函数是python内置的28     print("strings with length more than 5 and its length:", sep="\n")29     foreach(lambda e: print(e, sep="\n"), map(lambda e: (e, len(e)), filter(myList, lambda e: len(e) >= 5)))30 31     # 高阶函数,该函数定义为g(f(x)),其中函数f和g都是作为参数在调用该高阶函数时指定的32     def highOrderFunction1(x, f, g):33         return g(f(x))34 35     # 该函数在下面对highOrderFunction1函数的调用中被当做参数传入36     def f(x):37         return x + 138 39     # 第二个参数传入上面定义的f函数,作为第三个参数的的函数采用的是匿名函数40     result = highOrderFunction1(2, f, g=lambda x: x * x)41     print("f(x)=x+1, g(x)=x*x, g(f(2))=%d" % result, sep="\n")42 43     # 定义嵌套函数,针对每个参数,外层函数都会返回一个函数,即内层函数44     # 这里factor是自由变量,number是绑定变量。45     # 闭包是一个函数,返回值依赖于声明在函数外部的一个或多个变量46     # 函数multiplier返回的函数就是闭包,factor就是外部的变量,也叫自由变量,number是绑定变量(形式参数)47     def multiplier(factor):48         def multiplyByFactor(number):49             return factor * number50         return multiplyByFactor51 52 53     print("multiplier(2)(3)=%s" % multiplier(2)(3))54     # double是一个函数,它将输入参数乘以2倍以后返回55     double = multiplier(2)56     print("double(3)=%s" % double(3))57 58     print("multiplier(3)(4)=%s" % multiplier(3)(4))59     # triple是一个函数,它将输入参数乘以3倍以后返回60     triple = multiplier(3)61     print("triple(4)=%s" % triple(4))62 63     # 第三个参数f是函数64     def add(x, y, f):65         return f(x) + f(y)66 67     print("2 ^ 2 + 3 ^2 = %s" % add(2, 3, lambda x: x * x))68     print("(2 + 1) + (3 + 1) = %s" % add(2, 3, f))

最后来看一个数学题目,已知a<=b, 且a和b都是整数,求下面三个公式的值。

可以看到这三个公式分别是求a~b的和,a~b的平方和,a~b各自的阶乘的和。可以看到三个公式是类似的,都是求和,只不过分别是对自身求和,对自身的平方求和以及对自身的阶乘求和,也就是说这里有3个计算逻辑,需要对这3个计算逻辑计算出来的数求和。如果是指令式编程风格,就只能写三个函数来解决问题。但是如果采用函数式编程风格,就可以只写一个通用的求和函数来解决该问题,因为可以将这3个计算逻辑(函数)作为参数传给之前的通用求和函数。下面分别用Java,Scala和Python来解决该问题。

Java代码

1 package lxy.java.fp; 2  3 import java.util.function.*; 4  5  6 public class Sum4Integers { 7     //通用求和函数,其中f是一个函数式接口,它接收一个整型参数并返回一个整型数值 8     //采用递归计算方法 9     private static int sum(Function<Integer, Integer> f, int a, int b) {10         if (a > b)11             return 0;12         else13             return f.apply(a) + sum(f, a + 1, b);14     }15 16     //求阶乘函数,采用递归算法,较复杂,不能作为匿名函数传入上面的通用求和参数,因此需要预先定义17     private static int factor(int x) {18         if (x == 0)19             return 1;20         else21             return x * factor(x - 1);22     }23 24     //求公式一函数,即求a~b的和25     //作为参数的函数就是返回变量自身,较简单,采用匿名函数26     static int sumInts(int a, int b) {27         return sum(x -> x, a, b);28     }29 30     //求公式二函数,即求a^2 ~ b^2的和31     //作为参数的函数就是返回变量的平方,较简单,采用匿名函数32     static int sumSquares(int a, int b) {33         return sum(x -> x * x, a, b);34     }35 36     //求公式三函数,即求a! ~ b!的和37     //作为参数的函数就是求变量的阶乘,较复杂(本身是递归函数),采用定义好的函数factor38     static int sumFactors(int a, int b) {39         return sum(Sum4Integers::factor, a, b);40     }41 42     public static void main(String[] args) {43         System.out.println("1+2+3+4+5=" + sumInts(1, 5));44         System.out.println("1^2+2^2+3^2+4^2+5^2=" + sumSquares(1, 5));45         System.out.println("1!+2!+3!+4!+5!=" + sumFactors(1, 5));46     }47 48 }

Scala代码

1 package lxy.scala.fp 2  3  4 object Sum4Integers { 5     //通用求和函数 6     //采用递归计算方法 7     private def sum(f: Int => Int, a: Int, b: Int): Int = if (a > b) 0 else f(a) + sum(f, a + 1, b) 8  9     //求阶乘函数,采用递归算法,较复杂,不能作为匿名函数传入上面的通用求和参数,因此需要预先定义10     private def factor(x: Int): Int = if (x == 0) 1 else x * factor(x - 1)11 12     //求公式一函数,即求a~b的和13     //作为参数的函数就是返回变量自身,较简单,采用匿名函数14     def sumInts(a: Int, b: Int) = sum(x => x, a, b)15 16     //求公式二函数,即求a^2 ~ b^2的和17     //作为参数的函数就是返回变量的平方,较简单,采用匿名函数18     def sumSquares(a: Int, b: Int) = sum(x => x * x, a, b)19 20     //求公式三函数,即求a! ~ b!的和21     //作为参数的函数就是求变量的阶乘,较复杂(本身是递归函数),采用定义好的函数factor22     def sumFactors(a: Int, b: Int) = sum(factor, a, b)23 24     def main(args: Array[String]): Unit = {25         println("1+2+3+4+5=" + sumInts(1, 5))26         println("1^2+2^2+3^2+4^2+5^2=" + sumSquares(1, 5))27         println("1!+2!+3!+4!+5!=" + sumFactors(1, 5))28     }29 30 }

Python代码

1 #!/usr/bin/env python3 2 # -*- coding: utf-8 -*- 3  4 if __name__ == "__main__": 5     """ 6         Usage: ./sum_integers.py 7     """ 8  9     # 通用求和函数,采用递归计算方法10     def sum(f, a, b):11         if a > b:12             return 013         else:14             return f(a) + sum(f, a + 1, b)15 16     # 求阶乘函数,采用递归算法,较复杂,不能作为匿名函数传入上面的通用求和参数,因此需要预先定义17     def factor(x):18         if x == 0:19             return 120         else:21             return x * factor(x - 1)22 23     # 求公式一函数,即求a~b的和24     # 作为sum函数的第一个参数的函数就是返回变量自身,较简单,采用匿名函数25     def sumInts(a, b):26         return sum(lambda x: x, a, b)27 28     # 求公式二函数,即求a^2 ~ b^2的和29     # 作为sum函数的第一个参数的函数就是返回变量的平方,较简单,采用匿名函数30     def sumSquares(a, b):31         return sum(lambda x: x * x, a, b)32 33     # 求公式三函数,即求a! ~ b!的和34     # 作为sum函数的第一个参数的函数就是求变量的阶乘,较复杂(本身是递归函数),采用定义好的函数factor35     def sumFactors(a, b):36         return sum(factor, a, b)37 38     print("1+2+3+4+5=%d" % sumInts(1, 5))39     print("1^2+2^2+3^2+4^2+5^2=%d" % sumSquares(1, 5))40     print("1!+2!+3!+4!+5!=%d" % sumFactors(1, 5))

从上面解决同一个问题的代码量比较来看,Scala和Python比较短,而Java比较长,而且Java对函数式编程的支持目前还比较有限,因此函数式编程建议采用Scala或者Python。

(0)

相关推荐

  • python——函数

    一.定义函数 # 定义函数def say_hello(): print('hello') print('hi') # 调用函数,函数被调用之后才会生效say_hello()1234567 二.求和函数 ...

  • Go 面试题 013:Go 中闭包的底层原理是?

    大家好,我是明哥. 欢迎大家再次来到  『Go 语言面试题库』 这个专栏 本篇问题:Go 中闭包的底层原理? # 1. 什么是闭包? 一个函数内引用了外部的局部变量,这种现象,就称之为闭包. 例如下面 ...

  • 浅谈“计算机编程从娃娃抓起”

    来源:用户上传      作者: 肖亮英 摘 要:信息时代的发展使得编程成为核心能力,计算机编程从娃娃抓起,不只是对孩子负责,更是对未来负责.从为什么学编程.编程学什么.怎样学编程三个维度论述&quo ...

  • 【STL 源码剖析】浅谈 STL 迭代器与 traits 编程技法

    大家好,我是小贺. 点赞再看,养成习惯 文章每周持续更新,可以微信搜索「herongwei」第一时间阅读和催更,本文 GitHub : https://github.com/rongweihe/Mor ...

  • 浅谈ST 语言编程

    梯形图语言编程是PLC编程首选语言之一,其图形化的形象结构完全符合电工继电器控制电路当中的思路模式,备受工程师青睐! 而很多实际应用场合当中,需要用到很多复杂的算法来分析工艺要求,计算实际数据.如果采 ...

  • 嵌入式系统架构浅谈:编程设计模式 (一)---访问硬件的设计模式

    这系列开始谈软件上面的设计,对设计模式在面向对象里面应该各位都知道,或许你在实际开发当中用到,也或许你见过别人的代码中用到.当你程序的代码足够庞大的时候,你会发现维护寸步难行,牵一发而动全身,这个时候 ...

  • 浅谈乡村治理模式发生了哪些变化?

    随着乡村现代化的发展,越来越多的乡村走上了建设数字乡村的道路,从传统的乡村治理到使用互联网数字化治理的模式,乡村的风貌和农民的生活也发生了巨大的变化,接下来就让我们一起来了解一下乡村治理模式到底发生了 ...

  • 汽车是怎么开发出来的?浅谈汽车开发流程

    许良  汽车话题下的优秀答主你知道汽车是怎么开发出来的吗?你的脑海中很可能浮现出来这样一个画面:一个非常有艺术气息的设计师,在草图上帅气的描绘着看起来非常犀利的线条.对,但不全对.对于汽车工程师的我而 ...

  • 浅谈办公室装修的发展前景和趋势

    未来办公室装修的发展趋势会是怎样的?这是这个行业未来前景的重要话题.在这样一个新时代里,所有的事物都会以最新颖的方式出发.科技的发展也让每个行业都转遍了方向,同时对行业的要求和品质也有了更高的要求. ...

  • 颧骨浅谈

    ​骨过高 颧骨过于发达的人,单从脸部看上去就给人以高傲的感觉.而他们也得确有这种个性,常固执已见,虚张声势或显得自负是他们的特色.在工作上,也不愿意接受他人的忠告,总认为自己的就是最好的,这一点不利于 ...

  • 浅谈地龙在治疗咳喘中的运用

    浅谈地龙在治疗咳喘中的运用 笔者从事中医临床工作十多年来,在运用地龙治疗痰湿壅肺型.肺络瘀阻型.心肺两虚型.肾不纳气型的咳喘上有了一些心得,现介绍如下.   咳喘是现代医学中呼吸系统.心血管系统疾病的 ...