Ruby虽然是一个完全面向对象的语言,但是在它的发展过程中,也在不断的借鉴函数式编程的思想,来帮助改进语言的特性。其中最具代表性的就是block块,它就是Ruby世界中的匿名函数。那么为什么Ruby要不断的引入一些,函数式编程语言的特性呢,以及它都已经支持了哪些特性,本文就这一话题来聊一聊。
为何函数式
随着摩尔定律的失效,以及多核CPU的发展,如何高效的利用多核CPU的性能,已经是编程语言领域的一个重要话题了,想要利用多核性能,那么就必须很好的支持并发,但是目前主流的面向对象语言,并不是为并行而生的(面向对象语言最早是设计用于GUI编程的),比如多线程中的数据可变性带来的问题,而在函数式编程,所以状态都是不可变的,所以就不会出现这种问题。还有函数是确定抽象并且构建函数,再利用函数,构建更为复杂的抽象,不断的以函数围绕数据为核心的编程模式,其每一个单元(函数)都可独立运行(在不同线程中)这样就能更好的利用多核性能。
下面我就来介绍Ruby支持的函数式编程特性
函数一等公民
函数是一等公民,其意思就是函数可以向其他的数据类型一样,可以被计算,传递,函数返回等。在Ruby本身是不支持函数一等公民的,但是Ruby通过提供了Proc对象,可以使用它来作为函数的包装类,从而能够实现类似一等公民的计算。
表达式求值
函数式编程中,所以代码都是表达式,没有语句的概念,即所有的代码都可以求值,在Ruby中也是同样的设计,Ruby不管是,方法,类,变量,字面量等有值之外,控制结构,if case loop 等,也是可求值的。
result = if num.even?
num ** 2
else
num ** 3
end
高阶函数
高阶函数由两个部分组成,一个是函数作为参数,被传入到函数中,另一个是,一个函数以另一个函数作为返回值。这里被传入的函数,也分为普通函数和匿名函数。Ruby本并不支持函数直接调用和传递,但是它提供了Proc对象,我们可以是使用它去模拟函数的传递,函数作为参数,而对于匿名函数Ruby是通过block去支持的。
square = -> (ele) { ele*ele }
def sum(array, &fun)
array.map { |a| fun.(a) }.reduce(0, :+)
end
sum([1, 2], &square) # 求出平方和
#=> 5
# 匿名函数式的调用,求立方和
sum([1, 2, 3]) do |num|
num ** 3
end
#=> 36
柯里化
函数式编程中的柯里化是是指,对一个函数,只传递一部分参数来调用它,让它返回一个新函数去处理剩下的参数,直到最后一个参数被传递,函数返回结果。在柯里化中,可以一次性传递所有参数,也可以每次只传递一个参数。
Ruby中函数柯里化是通过Proc#curry方法实现的,也就是说明如果要应用柯里化,就要先传换成Proc对象
Proc#curry,Ruby中proc对象提供了curry方法用于实现柯里化。实例:
add = -> (x, y) { x + y }
increment = add.curry[1]
add_10 = add.curry[10]
increment.(4) #=> 5
add_10.(20) #=> 30
可以看到,proc的curry方法可以是以多维数组的方式,实现柯里化调用,同样也可以使用常规的proc调用方式。
组合
组合(compose)其就像一系列管道那样把不同的函数联系在一起,每个函数都将前一个函数的输出,作为自己的输入,就行Unix管道,这样数据就可以也必须在其中流动。
ruby 本身并没有提供组合函数,不过我们可以通过使用Proc#call,自己实现compose函数。
def compose(*procs)
-> (*args) do
if procs.size == 2
procs[1].call(procs[0].call(*args))
else
procs.pop.call compose(*procs).call(*args)
end
end
end
然后我们就可以是用组合函数进行函数式编程了
square = -> (x) { x*x }
sum = -> (x,y) { x + y }
sum_square = compose(sum, square)
sum_square.(1,2) #=> 9
sum_squrre_to_s = compose(sum, square, :to_s.to_proc)
sum_squrre_to_s.(1,2) #=> '9'
总结
函数式编程能够很好的助力,现在的软件开发,它和面向对象编程,两者是存在互补关系,这也是为什么近年来新出现的语言和改进的就语言都或多或少的加入了,一些函数式编程的特性。就如开头所说的未来函数式编程还会有更多的应用,但是我们不要教条化的认为只要是函数式就是好的,而是更应该在编程实践中去同时运用它们,来实现更好的软件。