函数式编程 订阅
函数式编程是种编程方式,它将电脑运算视为函数的计算。函数编程语言最重要的基础是λ演算(lambda calculus),而且λ演算的函数可以接受函数当作输入(参数)和输出(返回值)。 [1]  和指令式编程相比,函数式编程强调函数的计算比指令的执行重要。和过程化编程相比,函数式编程里函数的计算可随时调用。 展开全文
函数式编程是种编程方式,它将电脑运算视为函数的计算。函数编程语言最重要的基础是λ演算(lambda calculus),而且λ演算的函数可以接受函数当作输入(参数)和输出(返回值)。 [1]  和指令式编程相比,函数式编程强调函数的计算比指令的执行重要。和过程化编程相比,函数式编程里函数的计算可随时调用。
信息
外文名
Functional Programming
类    型
编程范式
原    理
将电脑运算视为函数的计算
中文名
函数式编程
主要思想
把运算过程写成嵌套函数调用
基    础
λ演算
函数式编程历史
虽然 λ 演算并非设计来于计算机上执行,但可视为第一个函数式编程语言。1980年代末期,Haskell发布企图集合很多函数式编程研究里的想法。 [2] 
收起全文
精华内容
参与话题
问答
  • 什么是函数式编程

    万次阅读 2018-08-22 11:21:35
    当我们说起函数式编程来说,我们会看到如下函数式编程的长相: 函数式编程的三大特性: immutable data 不可变数据:像Clojure一样,默认上变量是不可变的,如果你要改变变量,你需要把变量copy出去修改。这样一来...

    当我们说起函数式编程来说,我们会看到如下函数式编程的长相:

    • 函数式编程的三大特性:
      • immutable data 不可变数据:像Clojure一样,默认上变量是不可变的,如果你要改变变量,你需要把变量copy出去修改。这样一来,可以让你的程序少很多Bug。因为,程序中的状态不好维护,在并发的时候更不好维护。(你可以试想一下如果你的程序有个复杂的状态,当以后别人改你代码的时候,是很容易出bug的,在并行中这样的问题就更多了)
      • first class functions:这个技术可以让你的函数就像变量一样来使用。也就是说,你的函数可以像变量一样被创建,修改,并当成变量一样传递,返回或是在函数中嵌套函数。这个有点像Javascript的Prototype(参看Javascript的面向对象编程
      • 尾递归优化:我们知道递归的害处,那就是如果递归很深的话,stack受不了,并会导致性能大幅度下降。所以,我们使用尾递归优化技术——每次递归时都会重用stack,这样一来能够提升性能,当然,这需要语言或编译器的支持。Python就不支持。
    • 函数式编程的几个技术
      • map & reduce :这个技术不用多说了,函数式编程最常见的技术就是对一个集合做Map和Reduce操作。这比起过程式的语言来说,在代码上要更容易阅读。(传统过程式的语言需要使用for/while循环,然后在各种变量中把数据倒过来倒过去的)这个很像C++中的STL中的foreach,find_if,count_if之流的函数的玩法。
      • pipeline:这个技术的意思是,把函数实例成一个一个的action,然后,把一组action放到一个数组或是列表中,然后把数据传给这个action list,数据就像一个pipeline一样顺序地被各个函数所操作,最终得到我们想要的结果。
      • recursing 递归 :递归最大的好处就简化代码,他可以把一个复杂的问题用很简单的代码描述出来。注意:递归的精髓是描述问题,而这正是函数式编程的精髓。
      • currying:把一个函数的多个参数分解成多个函数, 然后把函数多层封装起来,每层函数都返回一个函数去接收下一个参数这样,可以简化函数的多个参数。在C++中,这个很像STL中的bind_1st或是bind2nd。
      • higher order function 高阶函数:所谓高阶函数就是函数当参数,把传入的函数做一个封装,然后返回这个封装函数。现象上就是函数传进传出,就像面向对象对象满天飞一样。

     

    • 还有函数式的一些好处
      • parallelization 并行:所谓并行的意思就是在并行环境下,各个线程之间不需要同步或互斥。
      • lazy evaluation 惰性求值:这个需要编译器的支持。表达式不在它被绑定到变量之后就立即求值,而是在该值被取用的时候求值,也就是说,语句如x:=expression; (把一个表达式的结果赋值给一个变量)明显的调用这个表达式被计算并把结果放置到 x 中,但是先不管实际在 x 中的是什么,直到通过后面的表达式中到 x 的引用而有了对它的值的需求的时候,而后面表达式自身的求值也可以被延迟,最终为了生成让外界看到的某个符号而计算这个快速增长的依赖树。
      • determinism 确定性:所谓确定性的意思就是像数学那样 f(x) = y ,这个函数无论在什么场景下,都会得到同样的结果,这个我们称之为函数的确定性。而不是像程序中的很多函数那样,同一个参数,却会在不同的场景下计算出不同的结果。所谓不同的场景的意思就是我们的函数会根据一些运行中的状态信息的不同而发生变化。

    上面的那些东西太抽象了,还是让我们来循序渐近地看一些例子吧。

    我们先用一个最简单的例子来说明一下什么是函数式编程。

    先看一个非函数式的例子:

    1

    2

    3

    4

    int cnt;

    void increment(){

        cnt++;

    }

    那么,函数式的应该怎么写呢?

    1

    2

    3

    int increment(int cnt){

        return cnt+1;

    }

    你可能会觉得这个例子太普通了。是的,这个例子就是函数式编程的准则:不依赖于外部的数据,而且也不改变外部数据的值,而是返回一个新的值给你

    我们再来看一个简单例子:

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    def inc(x):

        def incx(y):

            return x+y

        return incx

     

    inc2 = inc(2)

    inc5 = inc(5)

     

    print inc2(5) # 输出 7

    print inc5(5) # 输出 10

    我们可以看到上面那个例子inc()函数返回了另一个函数incx(),于是我们可以用inc()函数来构造各种版本的inc函数,比如:inc2()和inc5()。这个技术其实就是上面所说的Currying技术。从这个技术上,你可能体会到函数式编程的理念:把函数当成变量来用,关注于描述问题而不是怎么实现,这样可以让代码更易读。

    Map & Reduce

    在函数式编程中,我们不应该用循环迭代的方式,我们应该用更为高级的方法,如下所示的Python代码

    1

    2

    3

    name_len = map(len, ["hao", "chen", "coolshell"])

    print name_len

    # 输出 [3, 4, 9]

    你可以看到这样的代码很易读,因为,这样的代码是在描述要干什么,而不是怎么干

    我们再来看一个Python代码的例子:

    1

    2

    3

    4

    5

    6

    def toUpper(item):

          return item.upper()

     

    upper_name = map(toUpper, ["hao", "chen", "coolshell"])

    print upper_name

    # 输出 ['HAO', 'CHEN', 'COOLSHELL']

    顺便说一下,上面的例子个是不是和我们的STL的transform有些像?

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    #include <iostream>

    #include <algorithm>

    #include <string>

    using namespace std;

     

    int main() {

      string s="hello";

      string out;

      transform(s.begin(), s.end(), back_inserter(out), ::toupper);

      cout << out << endl;

      // 输出:HELLO

    }

    在上面Python的那个例子中我们可以看到,我们写义了一个函数toUpper,这个函数没有改变传进来的值,只是把传进来的值做个简单的操作,然后返回。然后,我们把其用在map函数中,就可以很清楚地描述出我们想要干什么。而不会去理解一个在循环中的怎么实现的代码,最终在读了很多循环的逻辑后才发现原来是这个或那个意思。 下面,我们看看描述实现方法的过程式编程是怎么玩的(看上去是不是不如函数式的清晰?):

    1

    2

    3

    4

    upname =['HAO', 'CHEN', 'COOLSHELL']

    lowname =[]

    for i in range(len(upname)):

        lowname.append( upname[i].lower() )

    对于map我们别忘了lambda表达式:你可以简单地理解为这是一个inline的匿名函数。下面的lambda表达式相当于:def func(x): return x*x

    1

    2

    3

    squares = map(lambda x: x * x, range(9))

    print squares

    # 输出 [0, 1, 4, 9, 16, 25, 36, 49, 64]

    我们再来看看reduce怎么玩?(下面的lambda表达式中有两个参数,也就是说每次从列表中取两个值,计算结果后把这个值再放回去,下面的表达式相当于:((((1+2)+3)+4)+5) )

    1

    2

    print reduce(lambda x, y: x+y, [1, 2, 3, 4, 5])

    # 输出 15

    Python中的除了map和reduce外,还有一些别的如filter, find, all, any的函数做辅助(其它函数式的语言也有),可以让你的代码更简洁,更易读。 我们再来看一个比较复杂的例子:

    计算数组中正数的平均值

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    num =[2, -5, 9, 7, -2, 5, 3, 1, 0, -3, 8]

    positive_num_cnt = 0

    positive_num_sum = 0

    for i in range(len(num)):

        if num[i] > 0:

            positive_num_cnt += 1

            positive_num_sum += num[i]

     

    if positive_num_cnt > 0:

        average = positive_num_sum / positive_num_cnt

     

    print average

    # 输出 5

    如果用函数式编程,这个例子可以写成这样:

    1

    2

    positive_num = filter(lambda x: x>0, num)

    average = reduce(lambda x,y: x+y, positive_num) / len( positive_num )

    C++11玩的法:

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    #include <iostream>

    #include <algorithm>

    #include <numeric>

    #include <string>

    #include <vector>

    using namespace std;

     

    vector num {2, -5, 9, 7, -2, 5, 3, 1, 0, -3, 8};

    vector p_num;

    copy_if(num.begin(), num.end(), back_inserter(p_num), [](int i){ return (i>0);} );

    int average = accumulate(p_num.begin(), p_num.end(), 0) / p_num.size();

    cout << "averge: " << average << endl;

    我们可以看到,函数式编程有如下好处:

    1)代码更简单了。
    2)数据集,操作,返回值都放到了一起。
    3)你在读代码的时候,没有了循环体,于是就可以少了些临时变量,以及变量倒来倒去逻辑。
    4)你的代码变成了在描述你要干什么,而不是怎么去干。

    最后,我们来看一下Map/Reduce这样的函数是怎么来实现的(下面是Javascript代码)

    map函数

    1

    2

    3

    4

    5

    6

    7

    var map = function (mappingFunction, list) {

      var result = [];

      forEach(list, function (item) {

        result.push(mappingFunction(item));

      });

      return result;

    };

    下面是reduce函数的javascript实现(谢谢 @下雨在家 修正的我原来的简单版本)

    reduce函数

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    function reduce(actionFunction, list, initial){

        var accumulate;

        var temp;

        if(initial){

            accumulate = initial;

        }

        else{

            accumulate = list.shfit();

        }

        temp = list.shift();

        while(temp){

            accumulate = actionFunction(accumulate,temp);

            temp = list.shift();

        }

        return accumulate;

    };

    Declarative Programming vs Imperative Programming

    前面提到过多次的函数式编程关注的是:describe what to do, rather than how to do it. 于是,我们把以前的过程式的编程范式叫做 Imperative Programming – 指令式编程,而把函数式的这种范式叫做 Declarative Programming – 声明式编程。

    下面我们看一下相关的示例(本示例来自这篇文章 )。

    比如,我们有3辆车比赛,简单起见,我们分别给这3辆车有70%的概率可以往前走一步,一共有5次机会,我们打出每一次这3辆车的前行状态。

    对于Imperative Programming来说,代码如下(Python):

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    from random import random

     

    time = 5

    car_positions = [1, 1, 1]

     

    while time:

        # decrease time

        time -= 1

     

        print ''

        for i in range(len(car_positions)):

            # move car

            if random() > 0.3:

                car_positions[i] += 1

     

            # draw car

            print '-' * car_positions[i]

    我们可以把这个两重循环变成一些函数模块,这样有利于我们更容易地阅读代码:

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    21

    22

    23

    24

    25

    26

    from random import random

     

    def move_cars():

        for i, _ in enumerate(car_positions):

            if random() > 0.3:

                car_positions[i] += 1

     

    def draw_car(car_position):

        print '-' * car_position

     

    def run_step_of_race():

        global time

        time -= 1

        move_cars()

     

    def draw():

        print ''

        for car_position in car_positions:

            draw_car(car_position)

     

    time = 5

    car_positions = [1, 1, 1]

     

    while time:

        run_step_of_race()

        draw()

    上面的代码,我们可以从主循环开始,我们可以很清楚地看到程序的主干,因为我们把程序的逻辑分成了几个函数,这样一来,我们的代码逻辑也会变得几个小碎片,于是我们读代码时要考虑的上下文就少了很多,阅读代码也会更容易。不像第一个示例,如果没有注释和说明,你还是需要花些时间理解一下。而把代码逻辑封装成了函数后,我们就相当于给每个相对独立的程序逻辑取了个名字,于是代码成了自解释的

    但是,你会发现,封装成函数后,这些函数都会依赖于共享的变量来同步其状态。于是,我们在读代码的过程时,每当我们进入到函数里,一量读到访问了一个外部的变量,我们马上要去查看这个变量的上下文,然后还要在大脑里推演这个变量的状态, 我们才知道程序的真正逻辑。也就是说,这些函数间必需知道其它函数是怎么修改它们之间的共享变量的,所以,这些函数是有状态的

    我们知道,有状态并不是一件很好的事情,无论是对代码重用,还是对代码的并行来说,都是有副作用的。因此,我们要想个方法把这些状态搞掉,于是出现了我们的 Functional Programming 的编程范式。下面,我们来看看函数式的方式应该怎么写?

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    21

    22

    23

    24

    from random import random

     

    def move_cars(car_positions):

        return map(lambda x: x + 1 if random() > 0.3 else x,

                   car_positions)

     

    def output_car(car_position):

        return '-' * car_position

     

    def run_step_of_race(state):

        return {'time': state['time'] - 1,

                'car_positions': move_cars(state['car_positions'])}

     

    def draw(state):

        print ''

        print '\n'.join(map(output_car, state['car_positions']))

     

    def race(state):

        draw(state)

        if state['time']:

            race(run_step_of_race(state))

     

    race({'time': 5,

          'car_positions': [1, 1, 1]})

    上面的代码依然把程序的逻辑分成了函数,不过这些函数都是functional的。因为它们有三个症状:

    1)它们之间没有共享的变量。
    2)函数间通过参数和返回值来传递数据。
    3)在函数里没有临时变量。

    我们还可以看到,for循环被递归取代了(见race函数)—— 递归是函数式编程中带用到的技术,正如前面所说的,递归的本质就是描述问题是什么。

    Pipeline

    pipeline 管道借鉴于Unix Shell的管道操作——把若干个命令串起来,前面命令的输出成为后面命令的输入,如此完成一个流式计算。(注:管道绝对是一个伟大的发明,他的设哲学就是KISS – 让每个功能就做一件事,并把这件事做到极致,软件或程序的拼装会变得更为简单和直观。这个设计理念影响非常深远,包括今天的Web Service,云计算,以及大数据的流式计算等等)

    比如,我们如下的shell命令:

    1

    ps auwwx | awk '{print $2}' | sort -n | xargs echo

    如果我们抽象成函数式的语言,就像下面这样:

    1

    xargs(  echo, sort(n, awk('print $2', ps(auwwx)))  )

    也可以类似下面这个样子:

    1

    pids = for_each(result, [ps_auwwx, awk_p2, sort_n, xargs_echo])

    好了,让我们来看看函数式编程的Pipeline怎么玩?

    我们先来看一个如下的程序,这个程序的process()有三个步骤:

    1)找出偶数。
    2)乘以3
    3)转成字符串返回

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    21

    22

    23

    24

    def process(num):

        # filter out non-evens

        if num % 2 != 0:

            return

        num = num * 3

        num = 'The Number: %s' % num

        return num

     

    nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

     

    for num in nums:

        print process(num)

     

    # 输出:

    # None

    # The Number: 6

    # None

    # The Number: 12

    # None

    # The Number: 18

    # None

    # The Number: 24

    # None

    # The Number: 30

    我们可以看到,输出的并不够完美,另外,代码阅读上如果没有注释,你也会比较晕。下面,我们来看看函数式的pipeline(第一种方式)应该怎么写?

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    21

    def even_filter(nums):

        for num in nums:

            if num % 2 == 0:

                yield num

    def multiply_by_three(nums):

        for num in nums:

            yield num * 3

    def convert_to_string(nums):

        for num in nums:

            yield 'The Number: %s' % num

     

    nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

    pipeline = convert_to_string(multiply_by_three(even_filter(nums)))

    for num in pipeline:

        print num

    # 输出:

    # The Number: 6

    # The Number: 12

    # The Number: 18

    # The Number: 24

    # The Number: 30

    我们动用了Python的关键字 yield,这个关键字主要是返回一个Generator,yield 是一个类似 return 的关键字,只是这个函数返回的是个Generator-生成器。所谓生成器的意思是,yield返回的是一个可迭代的对象,并没有真正的执行函数。也就是说,只有其返回的迭代对象被真正迭代时,yield函数才会正真的运行,运行到yield语句时就会停住,然后等下一次的迭代。(这个是个比较诡异的关键字)这就是lazy evluation。

    好了,根据前面的原则——“使用Map & Reduce,不要使用循环”,那我们用比较纯朴的Map & Reduce吧。

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    def even_filter(nums):

        return filter(lambda x: x%2==0, nums)

     

    def multiply_by_three(nums):

        return map(lambda x: x*3, nums)

     

    def convert_to_string(nums):

        return map(lambda x: 'The Number: %s' % x,  nums)

     

    nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

    pipeline = convert_to_string(

                   multiply_by_three(

                       even_filter(nums)

                   )

                )

    for num in pipeline:

        print num

    但是他们的代码需要嵌套使用函数,这个有点不爽,如果我们能像下面这个样子就好了(第二种方式)。

    1

    2

    3

    pipeline_func(nums, [even_filter,

                         multiply_by_three,

                         convert_to_string])

    那么,pipeline_func 实现如下:

    1

    2

    3

    4

    def pipeline_func(data, fns):

        return reduce(lambda a, x: x(a),

                      fns,

                      data)

    好了,在读过这么多的程序后,你可以回头看一下这篇文章的开头对函数式编程的描述,可能你就更有感觉了。

    最后,我希望这篇浅显易懂的文章能让你感受到函数式编程的思想,就像OO编程,泛型编程,过程式编程一样,我们不用太纠结是不是我们的程序就是OO,就是functional的,我们重要的品味其中的味道

    参考

    补充:评论中redraiment这个评论大家也可以读一读。

    感谢谢网友S142857 提供的shell风格的python pipeline:

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    21

    22

    23

    24

    25

    26

    27

    28

    29

    30

    31

    32

    33

    34

    class Pipe(object):

        def __init__(self, func):

            self.func = func

     

        def __ror__(self, other):

            def generator():

                for obj in other:

                    if obj is not None:

                        yield self.func(obj)

            return generator()

     

    @Pipe

    def even_filter(num):

        return num if num % 2 == 0 else None

     

    @Pipe

    def multiply_by_three(num):

        return num*3

     

    @Pipe

    def convert_to_string(num):

        return 'The Number: %s' % num

     

    @Pipe

    def echo(item):

        print item

        return item

     

    def force(sqs):

        for item in sqs: pass

     

    nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

     

    force(nums | even_filter | multiply_by_three | convert_to_string | echo)

    (全文完)

     

    转载自酷 壳 – CoolShell

    展开全文
  • Java函数式编程详解

    万次阅读 多人点赞 2019-05-05 21:46:49
    Java从1.8以后引入了函数式编程,这是很大的一个改进。函数式编程的优点在提高编码的效率,增强代码的可读性。本文历时两个多月一点点写出来,即作为心得,亦作为交流。 1.Java函数式编程的语法: 使用Consumer...

    Java从1.8以后引入了函数式编程,这是很大的一个改进。函数式编程的优点在提高编码的效率,增强代码的可读性。本文历时两个多月一点点写出来,即作为心得,亦作为交流。

    1.Java函数式编程的语法:

    使用Consumer作为示例,它是一个函数式接口,包含一个抽象方法accept,这个方法只有输入而无输出也就是说这个方法无返回值。 
    现在我们要定义一个Consumer接口的实例化对象,传统的方式是这样定义的:

    public static void main(String[] args) {
            //JDK1.8版本之前的做法
            Consumer<Integer> con = new Consumer<Integer>() {
                @Override
                public void accept(Integer t) {
                    System.out.println(t);
                }
            };
            //调用方法
            con.accept(3);
        }

    这里可以打印出3.

    上面是JDK1.8以前的做法,在1.8以后引入函数式的编程可以这样写:

    public static void main(String[] args) {
            //第一种写法:
            Consumer<Integer> con = (param) -> {System.out.println(param);}; 
            //第二种写法:
            Consumer<Integer> con1 = (param) -> System.out.println(param);
            //第三种写法:
            Consumer<Integer> con2 = System.out::println;
            
            con2.accept(3);
        }

    上面的con、con1、con2这个Consumer对象的引用照样可以打印出3.

    在上面的第二种写法是第一种写法的精简,但是只有在函数题中只有一条语句时才可以这么使用,做进一步的简化。

    在上面的第三种写法是对第一、二种写法的进一步精简,由于第三种写法只是进行打印,调用了System.out中的println静态方法对输入参数直接进行打印。它表示的意思就是针对输入的参数将其调用System.out中的静态方法println进行打印。

    上面已说明,函数式编程接口都只有一个抽象方法,因此在采用这种写法时,编译器会将这段函数编译后当作该抽象方法的实现。 
    如果接口有多个抽象方法,编译器就不知道这段函数应该是实现哪个方法的了。 
    因此,=  后面的函数体我们就可以看成是accept函数的实现。

    输入:-> 前面的部分,即被()包围的部分。此处只有一个输入参数,实际上输入是可以有多个的,如两个参数时写法:(a, b);当然也可以没有输入,此时直接就可以是()。
    函数体:->后面的部分,即被{}包围的部分;可以是一段代码。
    输出:函数式编程可以没有返回值,也可以有返回值。如果有返回值时,需要代码段的最后一句通过return的方式返回对应的值。但是Consumer这个函数式接口不能有具体的返回值。

    Java 8 中我们可以通过 `::` 关键字来访问类的构造方法,对象方法,静态方法。

    好了,到这一步就可以感受到函数式编程的强大能力。 
    通过最后一段代码,我们可以简单的理解函数式编程,Consumer接口直接就可以当成一个函数了,这个函数接收一个输入参数,然后针对这个输入进行处理;当然其本质上仍旧是一个对象,但我们已经省去了诸如老方式中的对象定义过程,直接使用一段代码来给函数式接口对象赋值。 
    而且最为关键的是,这个函数式对象因为本质上仍旧是一个对象,因此可以做为其它方法的参数或者返回值,可以与原有的代码实现无缝集成!


    2.Java函数式接口

    java.util.function.Consumer;
    java.util.function.Function;
    java.util.function.Predicate;

    2.1 Consumer是一个函数式编程接口; 顾名思义,Consumer的意思就是消费,即针对某个东西我们来使用它,因此它包含有一个有输入而无输出(无返回值)的accept接口方法; 
    除accept方法,它还包含有andThen这个方法; 

    JDK源码定义如下:

    default Consumer<T> andThen(Consumer<? super T> after) {
            Objects.requireNonNull(after);
            return (T t) -> { accept(t); after.accept(t); };

     }

    andThen这个方法是作用是:指定在调用当前Consumer后是否还要调用其它的Consumer; 

    public static void main(String[] args) {
            //定义第一个Consumer
            Consumer<Integer> consumer1 = (param) -> {System.out.println(param);};
            
            //定义第二个Consumer
            Consumer<Integer> consumer2 = (param) -> {System.out.println(param * param);};
            
            //consumer1可以连续的调用自己
            //consumer1.andThen(consumer1).andThen(consumer1).accept(3);
            //打印出 3 3 3
            
            //consumer1可以调用自己后调用consumer2
            consumer1.andThen(consumer1).andThen(consumer2).accept(3);
            //打印出3 3 9
        }

    注意:当一个Consumer接口调用另外一个Consumer对象时两个Counsumer对象的泛型必须一致。

    2.2  Function也是一个函数式编程接口;它代表的含义是“函数”,它是个接受一个参数并生成结果的函数,而函数经常是有输入输出的,因此它含有一个apply方法,包含一个输入与一个输出; 
    除apply方法外,它还有compose与andThen及indentity三个方法,其使用见下述示例;

    apply的用法:

    public static void main(String[] args) {
            Function<Integer, Integer> fun = res -> res + 1;
            Integer  i = fun.apply(2);
            System.out.println(i);
    }

    上面打印出2。

    Function编程接口有两个泛型Function<T, R> T表示:函数的输入类型,R表示:函数的输出类型。

    compose的用法:

    JDK源码定义:

    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
            Objects.requireNonNull(before);
            return (V v) -> apply(before.apply(v));

     }

    public static void main(String[] args) {
            Function<Integer, Integer> fun = res -> res + 1;
            Function<Integer, Integer> fun1 = res -> res * 10;
            Integer i = (Integer) fun.compose(fun1).apply(2);
            System.out.println(i);
    }

    上面打印出21;

    上面表示了fun2接收到2以后先计算,然后将fun2计算的结果再交给fun再计算。

    andThen方法的用法:

    JDK源码定义:

    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
            Objects.requireNonNull(after);
            return (T t) -> after.apply(apply(t));

     }

    public static void main(String[] args) {
            Function<Integer, Integer> fun = res -> res + 1;
            Function<Integer, Integer> fun1 = res -> res * 10;
            Integer i = (Integer) fun.andThen(fun1).apply(3);
            System.out.println(i);
     }

    上面打印出40

    上面表示了fun先计算得到4,然后将计算结果4再给fun1计算得到40;

    indentity方法的用法:

    JDK源码定义:

    static <T> Function<T, T> identity() {
            return t -> t;
     }

    public static void main(String[] args) {
            System.out.println(Function.identity().apply("总分"));

     }

    上面打印出“总分“;就是说传入什么参数输出什么参数。

    2.3 Predicate为函数式接口,predicate的中文意思是“断定”,即判断的意思,判断某个东西是否满足某种条件; 因此它包含test方法,根据输入值来做逻辑判断,其结果为True或者False。 

    test方法的用法:

    JDK源码定义: boolean test(T t);

    public static void main(String[] args) {
            Predicate<String> pre = res -> res.equals("1234");
            boolean rest = pre.test("1234");
            System.out.println(rest);
     }

    上面打印出true。

    and方法的用法:

    JDK源码定义:

    default Predicate<T> and(Predicate<? super T> other) {
            Objects.requireNonNull(other);
            return (t) -> test(t) && other.test(t);
     }

    public static void main(String[] args) {
            Predicate<String> pre = res -> res.equals("1234");
            Predicate<String> pre1 = res -> res.equals("1234");
            Predicate<String> pre3 = res -> res.equals("1234");
            boolean rest = pre.and(pre1).test("1234");
            boolean rest1 = pre.and(pre1).test("12341");
            System.out.println(rest);
            System.out.println(rest1); 

    }
    上面先打印出true,后打印出false

    根据源码,"1234"先和pre比较,如果为true,再和pre1比较。不为true直接返回false,不再和pre1比较。当“1234”和pre比较后为true,再和pre1比较结果为true,所以最终返回true。

    negate()的用法:

    JDK源码定义:

    default Predicate<T> negate() {
            return (t) -> !test(t);
     }

    public static void main(String[] args) {
            Predicate<String> pre = res -> res.equals("1234");
            boolean rest = pre.negate().test("1234");
            System.out.println(rest);
    }

    上面打印出false,上面的意思是如果比较的结果是true,那么返回false,如果为false,就返回true。

    or的方法的用法:

    JDK源码定义:

    default Predicate<T> or(Predicate<? super T> other) {
            Objects.requireNonNull(other);
            return (t) -> test(t) || other.test(t);
    }

    public static void main(String[] args) {
            Predicate<String> pre = res -> res.equals("1234");
            Predicate<String> pre1 = res -> res.equals("12341");
            boolean rest = pre.or(pre1).test("12341");
            System.out.println(rest);
     }

    上面打印出true;

    or方法的意思是:只要一个为true就返回true。

    isEqual方法用法:

    JDK源码定义:

    static <T> Predicate<T> isEqual(Object targetRef) {
            return (null == targetRef)
                    ? Objects::isNull
                    : object -> targetRef.equals(object);
     }

    public static void main(String[] args) {
            System.out.println(Predicate.isEqual("12345").test("12345"));

    }

    上面打印出true;isEqual里的参数是要比较的目标对象。

    3.函数式编程接口的使用

    通过Stream以及Optional两个类,可以进一步利用函数式接口来简化代码。

    3.1 Stream

    Stream可以对多个元素进行一系列的操作,也可以支持对某些操作进行并发处理。

    3.1.1 Stream对象的创建

    Stream对象的创建途径有以下几种:

    a. 创建空的Stream对象

    Stream str = Stream.empty();

    b. 通过集合类中的stream或者parallelStream方法创建;

    List<String> list = Arrays.asList("a", "b", "c", "d");
    Stream listStream = list.stream(); //获取串行的Stream对象
    Stream parallelListStream = list.parallelStream(); //获取并行的Stream对象  

    c. 通过Stream中的of方法创建:

    Stream s = Stream.of("test");

    Stream s1 = Stream.of("a", "b", "c", "d");

    d. 通过Stream中的iterate方法创建: 
        iterate方法有两个不同参数的方法:

    public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f);  
    public static<T> Stream<T> iterate(T seed, Predicate<? super T> hasNext, UnaryOperator<T> next)
    其中第一个方法将会返回一个无限有序值的Stream对象:它的第一个元素是seed,第二个元素是f.apply(seed); 第N个元素是f.apply(n-1个元素的值);生成无限值的方法实际上与Stream的中间方法类似,在遇到中止方法前一般是不真正的执行的。因此无限值的这个方法一般与limit等方法一起使用,来获取前多少个元素。 
    当然获取前多少个元素也可以使用第二个方法。 
    第二个方法与第一个方法生成元素的方式类似,不同的是它返回的是一个有限值的Stream;中止条件是由hasNext来断定的。
    第二种方法的使用示例如下:

    /**
     * 本示例表示从1开始组装一个序列,第一个是1,第二个是1+1即2,第三个是2+1即3..,直接10时中止;
     * 也可简化成以下形式:
     *        Stream.iterate(1,
     *        n -> n <= 10,
     *        n -> n+1).forEach(System.out::println);
     * 写成以下方式是为简化理解
     */
    Stream.iterate(1,
            new Predicate<Integer>() {
                @Override
                public boolean test(Integer integer) {
                    return integer <= 10;
                }
            },
        new UnaryOperator<Integer>() {
            @Override
            public Integer apply(Integer integer) {
                return integer+1;
            }
    }).forEach(System.out::println);

    e. 通过Stream中的generate方法创建 
    与iterate中创建无限元素的Stream类似,不过它的每个元素与前一元素无关,且生成的是一个无序的队列。也就是说每一个元素都可以随机生成。因此一般用来创建常量的Stream以及随机的Stream等。 
    示例如下:

    /**
     * 随机生成10个Double元素的Stream并将其打印
     */
    Stream.generate(new Supplier<Double>() {
        @Override
        public Double get() {
            return Math.random();
        }
    }).limit(10).forEach(System.out::println);

    //上述写法可以简化成以下写法:
    Stream.generate(() -> Math.random()).limit(10).forEach(System.out::println);


    f. 通过Stream中的concat方法连接两个Stream对象生成新的Stream对象 
    这个比较好理解不再赘述。

    3.1.2 Stream对象的使用
    Stream对象提供多个非常有用的方法,这些方法可以分成两类: 
    中间操作:将原始的Stream转换成另外一个Stream;如filter返回的是过滤后的Stream。 
    终端操作:产生的是一个结果或者其它的复合操作;如count或者forEach操作。

    其清单如下所示,方法的具体说明及使用示例见后文。 
    所有中间操作:

    所有的终端操作:

    下面就几个比较常用的方法举例说明其用法:

    3.1.2.1 filter
    用于对Stream中的元素进行过滤,返回一个过滤后的Stream 
    其方法定义如下:

    Stream<T> filter(Predicate<? super T> predicate);

    使用示例:

    Stream<String> s = Stream.of("test", "t1", "t2", "teeeee", "aaaa");
    //查找所有包含t的元素并进行打印
    s.filter(n -> n.contains("t")).forEach(System.out::println);

    3.1.2.2 map
    元素一对一转换。 
    它接收一个Funcation参数,用其对Stream中的所有元素进行处理,返回的Stream对象中的元素为Function对原元素处理后的结果 
    其方法定义如下:

    <R> Stream<R> map(Function<? super T, ? extends R> mapper);

    示例,假设我们要将一个String类型的Stream对象中的每个元素添加相同的后缀.txt,如a变成a.txt,其写法如下:

    Stream<String> s = Stream.of("test", "t1", "t2", "teeeee", "aaaa");
    s.map(n -> n.concat(".txt")).forEach(System.out::println);

    3.1.2.3 flatMap
    元素一对多转换:对原Stream中的所有元素使用传入的Function进行处理,每个元素经过处理后生成一个多个元素的Stream对象,然后将返回的所有Stream对象中的所有元素组合成一个统一的Stream并返回; 
    方法定义如下:

    <R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);

    示例,假设要对一个String类型的Stream进行处理,将每一个元素的拆分成单个字母,并打印:

    Stream<String> s = Stream.of("test", "t1", "t2", "teeeee", "aaaa");
    s.flatMap(n -> Stream.of(n.split(""))).forEach(System.out::println);

    3.1.2.4 takeWhile
    方法定义如下:

    default Stream<T> takeWhile(Predicate<? super T> predicate)

    如果Stream是有序的(Ordered),那么返回最长命中序列(符合传入的Predicate的最长命中序列)组成的Stream;如果是无序的,那么返回的是所有符合传入的Predicate的元素序列组成的Stream。 
    与Filter有点类似,不同的地方就在当Stream是有序时,返回的只是最长命中序列。 
    如以下示例,通过takeWhile查找”test”, “t1”, “t2”, “teeeee”, “aaaa”, “taaa”这几个元素中包含t的最长命中序列:

    Stream<String> s = Stream.of("test", "t1", "t2", "teeeee", "aaaa", "taaa");
    //以下结果将打印: "test", "t1", "t2", "teeeee",最后的那个taaa不会进行打印 
    s.takeWhile(n -> n.contains("t")).forEach(System.out::println);

    3.1.2.5 dropWhile
    与takeWhile相反,如果是有序的,返回除最长命中序列外的所有元素组成的Stream;如果是无序的,返回所有未命中的元素组成的Stream;其定义如下:

    default Stream<T> dropWhile(Predicate<? super T> predicate)

    如以下示例,通过dropWhile删除”test”, “t1”, “t2”, “teeeee”, “aaaa”, “taaa”这几个元素中包含t的最长命中序列:

    Stream<String> s = Stream.of("test", "t1", "t2", "teeeee", "aaaa", "taaa");
    //以下结果将打印:"aaaa", "taaa"  
    s.dropWhile(n -> n.contains("t")).forEach(System.out::println);

    3.1.2.6 reduce与collect
    关于reduce与collect由于功能较为复杂,在后续将进行单独分析与学习,此处暂不涉及。

    3.2 Optional用于简化Java中对空值的判断处理,以防止出现各种空指针异常。 
    Optional实际上是对一个变量进行封装,它包含有一个属性value,实际上就是这个变量的值。

    3.2.1 Optional对象创建
    它的构造函数都是private类型的,因此要初始化一个Optional的对象无法通过其构造函数进行创建。它提供了一系列的静态方法用于构建Optional对象:

    3.2.1.1 empty
    用于创建一个空的Optional对象;其value属性为Null。 
    如:

    Optional o = Optional.empty();

    3.2.1.2 of
    根据传入的值构建一个Optional对象; 
    传入的值必须是非空值,否则如果传入的值为空值,则会抛出空指针异常。 
    使用:

    o = Optional.of("test"); 
    1
    3.2.1.3 ofNullable
    根据传入值构建一个Optional对象 
    传入的值可以是空值,如果传入的值是空值,则与empty返回的结果是一样的。

    3.2.2 方法
    Optional包含以下方法:

    3.2.3 使用场景
    常用的使用场景如下:

    3.2.3.1 判断结果不为空后使用
    如某个函数可能会返回空值,以往的做法:

    String s = test();
    if (null != s) {
        System.out.println(s);
    }

    现在的写法就可以是:

    Optional<String> s = Optional.ofNullable(test());
    s.ifPresent(System.out::println);

    乍一看代码复杂度上差不多甚至是略有提升;那为什么要这么做呢? 
    一般情况下,我们在使用某一个函数返回值时,要做的第一步就是去分析这个函数是否会返回空值;如果没有进行分析或者分析的结果出现偏差,导致函数会抛出空值而没有做检测,那么就会相应的抛出空指针异常! 
    而有了Optional后,在我们不确定时就可以不用去做这个检测了,所有的检测Optional对象都帮忙我们完成,我们要做的就是按上述方式去处理。

    3.2.3.2 变量为空时提供默认值
    如要判断某个变量为空时使用提供的值,然后再针对这个变量做某种运算; 
    以往做法:

    if (null == s) {
        s = "test";
    }
    System.out.println(s);

    现在的做法:

    Optional<String> o = Optional.ofNullable(s);
    System.out.println(o.orElse("test"));

    3.2.3.3 变量为空时抛出异常,否则使用
    以往写法:

    if (null == s) {
        throw new Exception("test");
    }
    System.out.println(s);


    现在写法:

    Optional<String> o = Optional.ofNullable(s);
    System.out.println(o.orElseThrow(()->new Exception("test")));
     

    展开全文
  • 函数式编程入门教程

    千次阅读 2017-04-18 10:49:20
    你可能听说过函数式编程(Functional programming),甚至已经使用了一段时间。 但是,你能说清楚,它到底是什么吗? 网上搜索一下,你会轻松找到好多答案。 与面向对象编程(Object-oriented programming...

    作者: 阮一峰

    日期: 2017年2月22日

    你可能听说过函数式编程(Functional programming),甚至已经使用了一段时间。

    但是,你能说清楚,它到底是什么吗?

    网上搜索一下,你会轻松找到好多答案。

    • 与面向对象编程(Object-oriented programming)和过程式编程(Procedural programming)并列的编程范式。
    • 最主要的特征是,函数是第一等公民
    • 强调将计算过程分解成可复用的函数,典型例子就是map方法和reduce方法组合而成 MapReduce 算法
    • 只有纯的、没有副作用的函数,才是合格的函数。

    上面这些说法都对,但还不够,都没有回答下面这个更深层的问题。

    为什么要这样做?

    这就是,本文要解答的问题。我会通过最简单的语言,帮你理解函数式编程,并且学会它那些基本写法。

    需要声明的是,我不是专家,而是一个初学者,最近两年才真正开始学习函数式编程。一直苦于看不懂各种资料,立志要写一篇清晰易懂的教程。下面的内容肯定不够严密,甚至可能包含错误,但是我发现,像下面这样解释,初学者最容易懂。

    另外,本文比较长,阅读时请保持耐心。结尾还有 Udacity《前端工程师认证课程》的推广,非常感谢他们对本文的赞助。

    一、范畴论

    函数式编程的起源,是一门叫做范畴论(Category Theory)的数学分支。

    理解函数式编程的关键,就是理解范畴论。它是一门很复杂的数学,认为世界上所有的概念体系,都可以抽象成一个个的"范畴"(category)。

    1.1 范畴的概念

    什么是范畴呢?

    维基百科的一句话定义如下。

    "范畴就是使用箭头连接的物体。"(In mathematics, a category is an algebraic structure that comprises "objects" that are linked by "arrows". )

    也就是说,彼此之间存在某种关系的概念、事物、对象等等,都构成"范畴"。随便什么东西,只要能找出它们之间的关系,就能定义一个"范畴"。

    上图中,各个点与它们之间的箭头,就构成一个范畴。

    箭头表示范畴成员之间的关系,正式的名称叫做"态射"(morphism)。范畴论认为,同一个范畴的所有成员,就是不同状态的"变形"(transformation)。通过"态射",一个成员可以变形成另一个成员。

    1.2 数学模型

    既然"范畴"是满足某种变形关系的所有对象,就可以总结出它的数学模型。

    • 所有成员是一个集合
    • 变形关系是函数

    也就是说,范畴论是集合论更上层的抽象,简单的理解就是"集合 + 函数"。

    理论上通过函数,就可以从范畴的一个成员,算出其他所有成员。

    1.3 范畴与容器

    我们可以把"范畴"想象成是一个容器,里面包含两样东西。

    • 值(value)
    • 值的变形关系,也就是函数。

    下面我们使用代码,定义一个简单的范畴。

    
    class Category {
      constructor(val) { 
        this.val = val; 
      }
    
      addOne(x) {
        return x + 1;
      }
    }
    

    上面代码中,Category是一个类,也是一个容器,里面包含一个值(this.val)和一种变形关系(addOne)。你可能已经看出来了,这里的范畴,就是所有彼此之间相差1的数字。

    注意,本文后面的部分,凡是提到"容器"的地方,全部都是指"范畴"。

    1.4 范畴论与函数式编程的关系

    范畴论使用函数,表达范畴之间的关系。

    伴随着范畴论的发展,就发展出一整套函数的运算方法。这套方法起初只用于数学运算,后来有人将它在计算机上实现了,就变成了今天的"函数式编程"。

    本质上,函数式编程只是范畴论的运算方法,跟数理逻辑、微积分、行列式是同一类东西,都是数学方法,只是碰巧它能用来写程序。

    所以,你明白了吗,为什么函数式编程要求函数必须是纯的,不能有副作用?因为它是一种数学运算,原始目的就是求值,不做其他事情,否则就无法满足函数运算法则了。

    总之,在函数式编程中,函数就是一个管道(pipe)。这头进去一个值,那头就会出来一个新的值,没有其他作用。

    二、函数的合成与柯里化

    函数式编程有两个最基本的运算:合成和柯里化。

    2.1 函数的合成

    如果一个值要经过多个函数,才能变成另外一个值,就可以把所有中间步骤合并成一个函数,这叫做"函数的合成"(compose)。

    上图中,XY之间的变形关系是函数fYZ之间的变形关系是函数g,那么XZ之间的关系,就是gf的合成函数g·f

    下面就是代码实现了,我使用的是 JavaScript 语言。注意,本文所有示例代码都是简化过的,完整的 Demo 请看《参考链接》部分。

    合成两个函数的简单代码如下。

    
    const compose = function (f, g) {
      return function (x) {
        return f(g(x));
      };
    }
    

    函数的合成还必须满足结合律。

    
    compose(f, compose(g, h))
    // 等同于
    compose(compose(f, g), h)
    // 等同于
    compose(f, g, h)
    

    合成也是函数必须是纯的一个原因。因为一个不纯的函数,怎么跟其他函数合成?怎么保证各种合成以后,它会达到预期的行为?

    前面说过,函数就像数据的管道(pipe)。那么,函数合成就是将这些管道连了起来,让数据一口气从多个管道中穿过。

    2.2 柯里化

    f(x)g(x)合成为f(g(x)),有一个隐藏的前提,就是fg都只能接受一个参数。如果可以接受多个参数,比如f(x, y)g(a, b, c),函数合成就非常麻烦。

    这时就需要函数柯里化了。所谓"柯里化",就是把一个多参数的函数,转化为单参数函数。

    
    // 柯里化之前
    function add(x, y) {
      return x + y;
    }
    
    add(1, 2) // 3
    
    // 柯里化之后
    function addX(y) {
      return function (x) {
        return x + y;
      };
    }
    
    addX(2)(1) // 3
    

    有了柯里化以后,我们就能做到,所有函数只接受一个参数。后文的内容除非另有说明,都默认函数只有一个参数,就是所要处理的那个值。

    三、函子

    函数不仅可以用于同一个范畴之中值的转换,还可以用于将一个范畴转成另一个范畴。这就涉及到了函子(Functor)。

    3.1 函子的概念

    函子是函数式编程里面最重要的数据类型,也是基本的运算单位和功能单位。

    它首先是一种范畴,也就是说,是一个容器,包含了值和变形关系。比较特殊的是,它的变形关系可以依次作用于每一个值,将当前容器变形成另一个容器。

    上图中,左侧的圆圈就是一个函子,表示人名的范畴。外部传入函数f,会转成右边表示早餐的范畴。

    下面是一张更一般的图。

    上图中,函数f完成值的转换(ab),将它传入函子,就可以实现范畴的转换(FaFb)。

    3.2 函子的代码实现

    任何具有map方法的数据结构,都可以当作函子的实现。

    
    class Functor {
      constructor(val) { 
        this.val = val; 
      }
    
      map(f) {
        return new Functor(f(this.val));
      }
    }
    

    上面代码中,Functor是一个函子,它的map方法接受函数f作为参数,然后返回一个新的函子,里面包含的值是被f处理过的(f(this.val))。

    一般约定,函子的标志就是容器具有map方法。该方法将容器里面的每一个值,映射到另一个容器。

    下面是一些用法的示例。

    
    (new Functor(2)).map(function (two) {
      return two + 2;
    });
    // Functor(4)
    
    (new Functor('flamethrowers')).map(function(s) {
      return s.toUpperCase();
    });
    // Functor('FLAMETHROWERS')
    
    (new Functor('bombs')).map(_.concat(' away')).map(_.prop('length'));
    // Functor(10)
    

    上面的例子说明,函数式编程里面的运算,都是通过函子完成,即运算不直接针对值,而是针对这个值的容器----函子。函子本身具有对外接口(map方法),各种函数就是运算符,通过接口接入容器,引发容器里面的值的变形。

    因此,学习函数式编程,实际上就是学习函子的各种运算。由于可以把运算方法封装在函子里面,所以又衍生出各种不同类型的函子,有多少种运算,就有多少种函子。函数式编程就变成了运用不同的函子,解决实际问题。

    四、of 方法

    你可能注意到了,上面生成新的函子的时候,用了new命令。这实在太不像函数式编程了,因为new命令是面向对象编程的标志。

    函数式编程一般约定,函子有一个of方法,用来生成新的容器。

    下面就用of方法替换掉new

    
    Functor.of = function(val) {
      return new Functor(val);
    };
    

    然后,前面的例子就可以改成下面这样。

    
    Functor.of(2).map(function (two) {
      return two + 2;
    });
    // Functor(4)
    

    这就更像函数式编程了。

    五、Maybe 函子

    函子接受各种函数,处理容器内部的值。这里就有一个问题,容器内部的值可能是一个空值(比如null),而外部函数未必有处理空值的机制,如果传入空值,很可能就会出错。

    
    Functor.of(null).map(function (s) {
      return s.toUpperCase();
    });
    // TypeError
    

    上面代码中,函子里面的值是null,结果小写变成大写的时候就出错了。

    Maybe 函子就是为了解决这一类问题而设计的。简单说,它的map方法里面设置了空值检查。

    
    class Maybe extends Functor {
      map(f) {
        return this.val ? Maybe.of(f(this.val)) : Maybe.of(null);
      }
    }
    

    有了 Maybe 函子,处理空值就不会出错了。

    
    Maybe.of(null).map(function (s) {
      return s.toUpperCase();
    });
    // Maybe(null)
    

    六、Either 函子

    条件运算if...else是最常见的运算之一,函数式编程里面,使用 Either 函子表达。

    Either 函子内部有两个值:左值(Left)和右值(Right)。右值是正常情况下使用的值,左值是右值不存在时使用的默认值。

    
    class Either extends Functor {
      constructor(left, right) {
        this.left = left;
        this.right = right;
      }
    
      map(f) {
        return this.right ? 
          Either.of(this.left, f(this.right)) :
          Either.of(f(this.left), this.right);
      }
    }
    
    Either.of = function (left, right) {
      return new Either(left, right);
    };
    

    下面是用法。

    
    var addOne = function (x) {
      return x + 1;
    };
    
    Either.of(5, 6).map(addOne);
    // Either(5, 7);
    
    Either.of(1, null).map(addOne);
    // Either(2, null);
    

    上面代码中,如果右值有值,就使用右值,否则使用左值。通过这种方式,Either 函子表达了条件运算。

    Either 函子的常见用途是提供默认值。下面是一个例子。

    
    Either
    .of({address: 'xxx'}, currentUser.address)
    .map(updateField);
    

    上面代码中,如果用户没有提供地址,Either 函子就会使用左值的默认地址。

    Either 函子的另一个用途是代替try...catch,使用左值表示错误。

    
    function parseJSON(json) {
      try {
        return Either.of(null, JSON.parse(json));
      } catch (e: Error) {
        return Either.of(e, null);
      }
    }
    

    上面代码中,左值为空,就表示没有出错,否则左值会包含一个错误对象e。一般来说,所有可能出错的运算,都可以返回一个 Either 函子。

    七、ap 函子

    函子里面包含的值,完全可能是函数。我们可以想象这样一种情况,一个函子的值是数值,另一个函子的值是函数。

    
    function addTwo(x) {
      return x + 2;
    }
    
    const A = Functor.of(2);
    const B = Functor.of(addTwo)
    

    上面代码中,函子A内部的值是2,函子B内部的值是函数addTwo

    有时,我们想让函子B内部的函数,可以使用函子A内部的值进行运算。这时就需要用到 ap 函子。

    ap 是 applicative(应用)的缩写。凡是部署了ap方法的函子,就是 ap 函子。

    
    class Ap extends Functor {
      ap(F) {
        return Ap.of(this.val(F.val));
      }
    }
    

    注意,ap方法的参数不是函数,而是另一个函子。

    因此,前面例子可以写成下面的形式。

    
    Ap.of(addTwo).ap(Functor.of(2))
    // Ap(4)
    

    ap 函子的意义在于,对于那些多参数的函数,就可以从多个容器之中取值,实现函子的链式操作。

    
    function add(x) {
      return function (y) {
        return x + y;
      };
    }
    
    Ap.of(add).ap(Maybe.of(2)).ap(Maybe.of(3));
    // Ap(5)
    

    上面代码中,函数add是柯里化以后的形式,一共需要两个参数。通过 ap 函子,我们就可以实现从两个容器之中取值。它还有另外一种写法。

    
    Ap.of(add(2)).ap(Maybe.of(3));
    

    八、Monad 函子

    函子是一个容器,可以包含任何值。函子之中再包含一个函子,也是完全合法的。但是,这样就会出现多层嵌套的函子。

    
    Maybe.of(
      Maybe.of(
        Maybe.of({name: 'Mulburry', number: 8402})
      )
    )
    

    上面这个函子,一共有三个Maybe嵌套。如果要取出内部的值,就要连续取三次this.val。这当然很不方便,因此就出现了 Monad 函子。

    Monad 函子的作用是,总是返回一个单层的函子。它有一个flatMap方法,与map方法作用相同,唯一的区别是如果生成了一个嵌套函子,它会取出后者内部的值,保证返回的永远是一个单层的容器,不会出现嵌套的情况。

    
    class Monad extends Functor {
      join() {
        return this.val;
      }
      flatMap(f) {
        return this.map(f).join();
      }
    }
    

    上面代码中,如果函数f返回的是一个函子,那么this.map(f)就会生成一个嵌套的函子。所以,join方法保证了flatMap方法总是返回一个单层的函子。这意味着嵌套的函子会被铺平(flatten)。

    九、IO 操作

    Monad 函子的重要应用,就是实现 I/O (输入输出)操作。

    I/O 是不纯的操作,普通的函数式编程没法做,这时就需要把 IO 操作写成Monad函子,通过它来完成。

    
    var fs = require('fs');
    
    var readFile = function(filename) {
      return new IO(function() {
        return fs.readFileSync(filename, 'utf-8');
      });
    };
    
    var print = function(x) {
      return new IO(function() {
        console.log(x);
        return x;
      });
    }
    

    上面代码中,读取文件和打印本身都是不纯的操作,但是readFileprint却是纯函数,因为它们总是返回 IO 函子。

    如果 IO 函子是一个Monad,具有flatMap方法,那么我们就可以像下面这样调用这两个函数。

    
    readFile('./user.txt')
    .flatMap(print)
    

    这就是神奇的地方,上面的代码完成了不纯的操作,但是因为flatMap返回的还是一个 IO 函子,所以这个表达式是纯的。我们通过一个纯的表达式,完成带有副作用的操作,这就是 Monad 的作用。

    由于返回还是 IO 函子,所以可以实现链式操作。因此,在大多数库里面,flatMap方法被改名成chain

    
    var tail = function(x) {
      return new IO(function() {
        return x[x.length - 1];
      });
    }
    
    readFile('./user.txt')
    .flatMap(tail)
    .flatMap(print)
    
    // 等同于
    readFile('./user.txt')
    .chain(tail)
    .chain(print)
    

    上面代码读取了文件user.txt,然后选取最后一行输出。

    十、参考链接

    (正文完)

    ============================

    感谢你读完了全文。下面还有一个推广,请再花一分钟阅读。

    去年十月,我介绍了来自硅谷的技术学习平台优达学城(Udacity),他们推出的纳米学位

    现在,他们进入中国市场快满周年了,又有一个本地化课程发布了。那就是由 Google 和 Github 合作制作的"前端开发工程师"认证课程。

    展开全文
  • 你真的理解函数式编程吗?

    万次阅读 2018-04-12 10:43:24
    大数据以及人工智能越来越流程,你是否可以轻松适应大数据编程,函数式编程在其中起着重要作用,如何从面向对象编程跳槽到函数式编程?你是否觉得函数式各种概念难于理解?本场 Chat 将为你解答。我将为你分享亲身...

    大数据以及人工智能越来越流程,你是否可以轻松适应大数据编程,函数式编程在其中起着重要作用,如何从面向对象编程跳槽到函数式编程?你是否觉得函数式各种概念难于理解?本场 Chat 将为你解答。我将为你分享亲身学习和理解关于函数式编程的经验:

    • 高阶函数、闭包、匿名函数等
    • 高阶函数和闭包是啥关系?
    • 柯里化
    • 函数式编程思维

    适合人群:

    • 如果你想转人工智能领域,请关注此 Chat
    • 如果你想了解机器学习,请关注此 Chat
    • 如果你正在学习一下机器学习,请购关注 Chat

    实录提要:

    • 函数式编程在哪一方面用得比较多?
    • 学习算法有什么建议及过程分享,从 0 如何开始学习 Python?
    • 做过 Python,逻辑很强,但是算法不是很懂,写的时候不知如何下手?
    • 不同语言中函数的重要性有哪些?
    • 函数式编程算法和传统算法不太一样,是否要重新学习,有没有推荐的资料?
    • 对于大型项目,函数式编程有什么好的应用场景?
    • 想把已有的一个项目,用函数式编程的思想重新实现,有哪些需要注意的事项?
    • 函数式编程与面向对象编程那个实现的性能会比较好?
    • 函数式编程具体是在哪个数据分析过程中应用,是清洗还是算统计量的过程中?
    • 函数式编程跟目前面向对象/过程编程的主要思维方式上的区别是什么?
    • 对于前端工程师来说,函数式编程如何入门学习?
    • 函数式编程的概念是什么?
    • 函数式编程在什么情况下使用?
    • 如何看待函数式编程的实际应用的?
    • 函数式编程的学习的网上教程有什么好的推荐吗?或者书籍?

    前言

    现在机器学习、人工智能的发展趋势如火如萘,很多培训班也在引入大数据、机器学习的课程,受到众多IT从业者的追捧,有一种势必与传统模式一决高下,分享半壁江山之势,如果人工智能走向企业、社会,它带来的经济效益以及影响是巨大的,我们每个人不管从事什么行业都有必要了解一下人工智能的发展趋势,机器学习背后隐藏着什么不可告人的秘密,让我们来揭开层层面纱,一窥究竟。

    在面向对象语言满天飞的现在,很多人认为,函数式编程是一种仅仅存在于某些偏门语言中,然而,纵观现代主流语言都在引进函数式特性,不同语言可能引进的程度不同,如我们最熟悉的jdk,jdk8已经引入了函数式编程的一些特性,可见大公司在不断引进新技术,唯有跟着技术前沿发展,我们才能立足于不败之地。

    本篇文章的目录结构如下:

    enter image description here

    为什么函数式编程会在这个时期流行起来?这种思想出现至少10年以上了,那一定是它自身的某些特性符合了这个时代的特性,解决了企业中设计、开发中遇到的各种问题,这是一个流程技术的内在驱动力,在函数式编程里面函数的地位很高,如面向对象中的对象一样常见,有三个俗称三板斧的利器,map、filter、reduce,它将在以后我们的编程中随手不离,函数式编程为我们开启了另一个思考维度。

    思想转化

    计算机科学的进步经常是间歇式的发展,先形成好的思路、然后出来这种思路下面的语言,之后便会成为某一时代的主流,例如第一种面向对象的语言simula 1967年发明,直到1983年的面向对象的C++才流行起来,早年java总被认为太慢,编程迟钝,内存消耗太高不适合高性能的应用,如今计算机硬件已经不再是制约条件,硬件的变迁把它变为了极具吸引力的语言,从国内大部分公司应用即可看出来。

    那么大家想一下,如今硬件仍然在飞速发展,那么更高级的面向函数式编程、语言会不会更适应这个时代?

    函数式思想

    什么是函数式编程?

    百科定义:

    函数式编程是种编程方式,它将电脑运算视为函数的计算。函数编程语言最重要的基础是λ演算(lambda calculus),而且λ演算的函数可以接受函数当作输入(参数)和输出(返回值)。

    个人理解就是我们的编程是以函数作为单元来处理各个业务逻辑,函数既可以当做参数传来传去,也可以作为返回值,可以把函数理解一个值到另一个值得映射关系,由于函数式编程方式更适合于数据处理,随着存储器容量升高、计算机处理能力大幅提高,它的优势更加明显,最近支持函数式编程的语言也逐渐流行,比如python、scale等都因它们对函数式编程的支持被人们重视,从被遗忘的角落重新拾起。

    函数式编程因为其特点更适用于统计分析数据、科学计算、大数据处理等方面工作,当然并不限于这些,在web开发、服务器脚本等其它方面也很不错,而面向对象编程更适合于开发和处理业务性强、功能模块完备的大型业务系统。

    优势特点

    代码简洁、开发快速

    函数式代码同命令式相比代码量要少很多,一行顶十行,所以实现一些功能也比较简洁,作为开发者的我们,还是对代码更亲切一些,来看一个具体例子,从中体会其特点。功能描述:统计文本或网页中单词的频率TF(term frequency),词频在计算网页质量、信息检索中是一个重要概念,下面来看一下简化代码:

    命令式实现

    /** * Created by lilongsheng on 2017/11/5. */public class Words {    /**     * 虚词过滤     */    private static Set<String> NON_WORDS = new HashSet<String>(){        {add("the");add("and");add("of");add("to");add("a");         add("i");add("it");add("in");add("or");add("is");add("d");         add("s");add("as");add("so");add("but");add("be");}};    /**     * 命令式方式过滤实现      * @param words     * @return     */    public static Map wordFreq1(String words){        TreeMap<String,Integer> wordMap = new TreeMap<>();        Matcher m = Pattern.compile("\\w+").matcher(words);        while (m.find()){            String word = m.group().toLowerCase();            if (! NON_WORDS.contains(word)){                if (wordMap.get(word) == null){                    wordMap.put(word,1);                }else {                    wordMap.put(word,wordMap.get(word)+1);                }            }        }        return wordMap;    }

    函数式实现

        /**     * 将待处理对象转为列表集合     * @param words     * @param regex     * @return     */    private static List<String> regexToList(String words,String regex){        List wordList  = new ArrayList<>();        Matcher m = Pattern.compile(regex).matcher(words);        while (m.find())            wordList.add(m.group());        return wordList;    }    /**     * 对集合统一处理     * @param words     * @return     */    public static Map wordFreq2(String words){        TreeMap<String,Integer> wordMap = new TreeMap<>();        regexToList(words,"\\w+").stream()                .map(w -> w.toLowerCase())                .filter(w -> !NON_WORDS.contains(w))                .forEach(w -> wordMap.put(w,wordMap.getOrDefault(w,0)+1));        return wordMap;    }    public static void main(String[] args) {        String test = "Before the popularity of network communicative tools, such as Weibo and WeChat, people get used to know a person by face and face talk, but now the situation has changed, the young generation tend to know a person by network social communicative tools. If they are interested in making friends, then they will be friends online first and then keep trace with the former information on the record. It seems that we can learn a person so fast and convenient. There is a short video about a girl dated a guy, but the guy did not have any account on the Internet, then the girl felt not comfortable and started to question if the guy was a criminal. The video satirizes people to rely on the Internet too much. They rather to communicate with a person by the Internet instead of face and face talk, while the latter is much trustworthy";        System.out.println(wordFreq1(test).toString());        System.out.println(wordFreq2(test).toString());    }}

    函数式编程思维是对集合统一处理、统一操作,而命令式编程需要取出来每个单词单独处理,单独计数,而函数式只需要传入待处理对象集合、处理规则,我们不需要关注于具体细节,这样编程不仅仅减少了出现bug的概率而且提高了IT人员开发效率,何乐而不为呢。

    易于理解,抽象度高

    让我们再来看一个在开发中,我们经常遇到场景,例如我们有一个List<User>列表,我们要把user的某个属性提取出来生成一个新的List,如下:

    import lombok.Data;/** * Created by lilongsheng on 2017/11/8. * 用户数据实体对象 */@Datapublic class User {    /**     * 主键     */    private Integer id;    /**     * 用户名     */    private String userName;    /**     * 用户密码     */    private String userPassword;    /**     * 年龄     */    private Integer age;    /**     * 电话     */    private String phone;    /*其它属性*/}public static void main(String[] args) {        //初始化数据        List<User> userList = new ArrayList<>();        for (int i = 0; i < 10; i++) {            User user = new User();            user.setUserName("lilongsheng" + i);            userList.add(user);        }        //命令式方式实现        List<String> newUserNameList1 = new ArrayList<>();        for (int i=0;i<userList.size();i++){            User user = userList.get(i);            newUserNameList1.add(user.getUserName());        }        //函数式方式实现 java 8 funcational        List<String> newUserNameList2 = userList.stream()                                                .map(p -> p.getUserName())                                                .collect(Collectors.toList());    }

    假设你已经了解了函数式语言的语法,你可能会觉得函数式写法很简洁,函数式编程并不需要你关注细节实现,我们在获取用户名作为一个新List时并没有对单独user对象操作,而是是告诉集合对象,我们要做什么,思维重心和关注点从“怎么做”转移到了“做什么”,通过map这个高阶函数把集合以及怎么做的规则传入,它帮我们处理集合元素,并返回一个结果集。

    没有副作用,变量无状态

    如果一个函数内外有依赖于外部变量或者环境时,常常我们称之为其有副作用,如果我们仅通过函数签名不打开内部代码检查并不能知道该函数在干什么,作为一个独立函数我们期望有明确的输入和输出,副作用是bug的发源地,作为程序员开发者应尽量少的开发有副作用的函数或方法,副作用也使得方法通用性下降不适合扩展和可重用性。

    函数式编程语言强烈要求使用者编写没有副作用的函数,它的函数式数学意义上的函数,给定一个输入就会有一个输出,而且每次相同的输入输出也肯定一样,函数式中的变量所代表的意义并不是内存中的一块存储单元,因此多个函数同时操作一个变量或者一个函数调用多次一个变量都不会改变该变量的值,函数每次对于给定的输入都是作为一个新值来处理,就是因为它这种没有状态、没有副作用的理念,很适合大数据计算和处理,它只接受固定输入既可以得到预定计算结果,完全不依赖于外部环境,由这个想到了吕迅说过“我们要做一个纯粹的人、做一个大写的人”,不能因为环境、外部压力所屈服,以后,就让我们来编写无副作用的函数吧。

    函数式编程其中的“函数”并不是我们计算机中理解的方法或函数,而是纯数学领域函数的概念,看一下百科中对数学函数的定义:

    函数的定义:

    给定一个数集A,对A施加对应法则f,记作f(A),得到另一数集B,也就是B=f(A)。那么这个关系式就叫函数关系式,简称函数。函数概念含有三个要素:定义域A、值域C和对应法则f。其中核心是对应法则f,它是函数关系的本质特征。

    命令式程序是基于冯诺依曼计算机结构为基础,一条一条的执行操作指令,程序执行效率为每条命令执行的总和,我们是面向命令编程,一般我们使用的语言都是命令式语言,比如c c++ java 等等,还有一种编程方式叫做逻辑式编程,代码风格和命令式一样,只是在思考过程的时候,更偏重于逻辑思想部分,对于复杂的问题可能会更有优势。

    命令式编程是计算机硬件的抽象,可以这样理解,我们把预先写好的一条条代码翻译成机器语言就是01指令,告诉计算机先执行哪个命令后执行哪个命令,最终是去改变了计算机硬件的稳定状态 1 稳定 0 不稳定 所以,可以说命令式编程是计算机硬件的抽象。

    函数式编程是对于数学的抽象,理论基础来源于数学基础。

    概念分析

    闭包

    定义:

    一种特殊的函数,绑定了函数内部引用的所有变量,把它引用的东西都放在一个上下文中“包”了起来。百科定义:包含两层含义要执行的代码块(自由变量以及自由变量引用的对象)和自由变量的作用域。

    闭包的概念在很多不同内容中都有提到,如数据库原理中函数依赖有闭包、JavaScript也有闭包、好多语言里面也会提到闭包的概念,它们得意思是否一样呢?答案是一样的,只是他们的表现形式不一样,数据库中的闭包是描述列是否可达、数据库范式的依据,把需要的东西放在了“上下文中”包了起来,解释闭包时先让我们来回顾一下前提条件:

    函数式编程以函数作为一等公民,以函数为单元完成各种操作,函数是自变量到因变量的一一映射,即一个值到另一个值得映射,接收一个值返回另一个值,好了前提条件已经有了让我们来完成一个函数,计算两个数的相加操作,如下:

    def plus(senior):    return senior + 100if  __name__ == '__main__':    result = plus(3)    print result

    由于函数只能接收一个参数,该函数只能计算加100,这一种类型加法,想一想怎么才能让它再接收一个参数不变的情况下,计算出结果呢,也许你会想到先把值保存起来,不过函数式编程是不保存计算中间结果的,即变量是不可变的,我们可以把这个函数处理结果传递给另一个函数(同样接收一个参数),这样一来即可计算两个数加法,如下:

    def plus(senior):    def pluaA(second):        return senior + second    return pluaAif  __name__ == '__main__':    pluaA = plus(3)    result = pluaA(2)    print result

    上面代码中plus即是一个闭包,闭包不仅仅是一个函数,从定义可以看出其包含几个部分,引用变量、代码块、作用域,可以结合上述代码加以理解。

    闭包函数里面的函数虽然有函数名字但是并没有意义,只是一个名称而已从外面不能直接访问,属于匿名函数,个人理解它属于一种特殊的函数以及该函数包含的作用域里面包含的东西,闭包是一种看不见摸不着的东西,因此不好理解,通过闭包可以将n个函数相互连接起来,可以无限相互之间结果进程映射,如果没有闭包那么数学中的函数将会是一个个死的函数式子,闭包是函数式编程的灵活、是函数式编程的核心。

    把代码绑定到闭包后,可以推迟到适当的时机再执行闭包,是一种对行为的建模手段,让我们把代码和上下文同时封装在单一结构,也就是闭包里面,以后执行。

    高阶函数

    高阶函数从字面上面理解除了普通函数功能还是高级内容,即可以把函数作为参数或者返回值的函数,从这个角度来说增强了函数处理能力,书上定义为可以接收函数为参数或者返回一个函数作为参数的函数。大家思考一下,高阶函数为什么可以这么设计,闭包在里面起到了什么作用?

    匿名函数

    匿名函数即lambda表达式函数,经常作为函数参数或者生成表达式的场景中,闭包中里面的函数可以看成是匿名函数来理解,上面计算两个数的加法还可以改为这样:

    def plus2(senior):    return lambda second:senior+secondif  __name__ == '__main__':    pluaA2 = plus2(3)    result = pluaA2(2)    print result

    这两种写法意思和作用是一样的,其实上面的PluaA也是匿名函数,只不过这样写会易于阅读和理解,匿名函数常用在比较简单的参数表达式中,使得代码简化、简洁,但是不要滥用匿名函数,如果代码中到处是匿名函数那么看起来会很不好理解。

    柯里化

    通俗的理解是将函数的参数变为一个参数的形式,函数式编程提倡柯里化编程,尽量编写一个参数的函数,优化方便,简化代码。

    语法糖

    指程序的可阅读性更高、可以给我们带来方便,更简洁的写法,提高开发编码效率某种语言中添加了某种语法,这种语法对功能没有影响,但可以提高可阅读性、间接性等,则称这种语法为该语言的语法糖

    性能优化

    缓求值

    是函数式编程语言的一种特性,这个特性也比较好理解,尽可能的推迟函数或表达式的计算过程,等到真正用到的时候才加载数据,类俗语hibernate框架中的懒加载,利用缓求值的方式来使用映射,可以产生更高效率的代码。

    尾递归、尾调用

    尾调用是在函数的尾部,调用另一个函数,因为函数是语言没有循环,递归很重要,对于递归需要来不断优化,一般采用尾调用或尾递归来优化。顾名思义,尾递归就是从最后开始计算, 每递归一次就算出相应的结果, 也就是说, 函数调用出现在调用者函数的尾部 ,尾递归就是把当前的运算结果(或路径)放在参数里传给下层函数和普通递归区别在于内存占用。

    总结

    函数式编程其特性非常适合于大数据处理、科学计算、数据统计等业务,随着人工智能、机器学习、深度学习的流行正在变得重要起来,从面相对象思维到函数式思维的转变,是我们更好的面对人工智能领域问题的催化剂,它会使我们从细节中解救出来以数学的思维考虑问题,不管你是从事哪个领域工作,都有必要了解一下智能时代发展的方向,因为未来你从事的职业很可能被智能化取代。


    本文首发于GitChat,未经授权不得转载,转载需与GitChat联系。

    阅读全文: http://gitbook.cn/gitchat/activity/59fe7550d343ff71a3c8620f

    您还可以下载 CSDN 旗下精品原创内容社区 GitChat App ,阅读更多 GitChat 专享技术内容哦。

    FtooAtPSkEJwnW-9xkCLqSTRpBKX

    展开全文
  • 函数式编程介绍

    万次阅读 多人点赞 2018-01-19 10:44:59
    历史来源 讲述历史来源,不喜欢的可以跳过。但是我个人认为这对理解有帮助。 在计算机的世界中,有两位巨擘对问题的可计算性做了模型化描述[4]。 一位是阿兰....计算机系的各种学科中都充斥着这个概念,假设有一个...
  • 浅析C++的函数式编程

    千次阅读 多人点赞 2019-04-26 14:08:34
    函数式编程(Functional Programming,FP)思想几乎成为了目前编程语言的下一个主要的演化趋势。Java8在Java中通过lambda表达式、Stream API引入了函数式编程,那么C++中是否也支持函数式编程呢?答案是肯定的。目前...
  • 全文共2055字,预计学习时长6分钟 图源:Pexels 托马斯.库尔提出“科学的革命”的范式论后,...最知名的三种范式分别是面向对象编程、指令式编程和函数式编程。它们并没有优劣之分,重要的是使用正确的工具来完...
  • 什么是函数式编程? C语言为何不是函数式语言? 函数式语言有两个个主要的特点:1. 函数是“头等公民”。2. 数据的“immutability”. 操作的“无副作用”, 这规避了‘锁’。 函数式编程的函数是指数学上的函数:...
  • 函数式编程思想概论

    千次阅读 2019-06-26 10:57:16
    函数式编程思想概论前言函数λ 演算λ项绑定变量和自由变量约简α 变换β 约简η 变换纯函数、副作用和引用透明性函数式编程与并发编程总结 原文地址 前言 在讨论函数式编程(Functional Programming)的具体内容...
  • 函数式编程

    2020-11-09 10:18:15
    一、函数式接口 1.1概念: 函数式接口在Java中是指:有且仅有一个抽象方法的接口。 1.2格式: 修饰符 interface 接口名称{ public abstract 返回值类型 方法名称(可选参数类型); //其他非抽象方法内容 } 其中 ...
  • 第9章 从面向对象到函数式编程 假如本书的写作时间倒退回十年前,书名可能会变成JavaScript面向对象编程思想。自上世纪90年代兴起的面向对象编程思想随Java的繁荣达于顶点,在JavaScript从一门只被用来编写零星的...
  • 什么是函数式编程? 函数式编程是一种面向函数和函数组合的编程方式。  什么是函数?从数学的角度,函数即Function,是从集合A到集合B的一种映射关系。如果集合A中的每一个元素都对应到集合B中的某一个元素,那么...
  • Scala中的函数式编程

    万次阅读 2018-11-19 16:24:19
    Scala中的函数式编程 Scala程序设计 第2版 - 原版.pdf 下载:https://download.csdn.net/download/u014646662/10805074 作为一门面向对象与函数式的混合范式语言,Scala 并不强制要求函数必须是纯函数,也不要求...
  • React中的函数式编程函数式编程定义的深刻解读 因为一直有了解函数式编程,也知道React是比较遵循函数式编程设计模式的框架,但是一直对函数是编程的定义和理解不是很清楚,到底函数式编程有上面优势,React如何...
  • JavaScript函数式编程

    千次阅读 2016-12-03 15:58:03
    JavaScript函数式编程摘要以往经常看到”函数式编程“这一名词,却始终没有花时间去学习,暑期实习结束之后一直忙于边养老边减肥,81天成功瘦身30斤+ ,开始回归正常的学习生活。 便在看《JavaScript函数式编程》这...
  • 现代C++函数式编程

    千次阅读 2016-08-22 19:14:56
    作者简介: 祁宇,武汉烽火云创软件技术... 导读: 本文作者从介绍函数式编程的概念入手,分析了函数式编程的表现形式和特性,最终通过现代C++的新特性以及一些模板云技巧实现了一个非常灵活的pipeline,展示了现代C...
  • Java函数式编程

    千次阅读 多人点赞 2017-02-26 23:08:52
    函数式编程是声明式的。也就是说,她应该指定“什么要做”而非“怎么做”。这种方式使得我们可以工作更高的抽象层次。而传统的过程式以及面向对象的语言,则是命令式的,因而更关注于“怎么做”这个层面。站在面向...
  • 欢迎来到函数式编程的世界。在这个只有函数的世界中,我们愉快地生活着,没有任何外部环境的依赖,没有状态,没有突变——永远没有。函数式编程是最近的一个热点。你可能在团队中和小组会议中听说过这个术语,或许还...
  • 函数式编程所描绘的范畴,是一个以数据类型为点,以函数为箭头,并且充满了自函子映射的范畴。 从范畴论的角度看函数式编程,会产生一种全新的理解。OO之后人们以构建和封装对象的角度来看待编程,一度使编程与数学...
  • 函数式编程

    千次阅读 2017-09-20 14:13:40
    "函数式编程", 又称泛函编程, 是一种"编程范式"(programming paradigm),也就是如何编写程序的方法论。它的基础是 λ 演算(lambda calculus)。λ演算可以接受函数当作输入(参数)和输出(返回值)。

空空如也

1 2 3 4 5 ... 20
收藏数 363,799
精华内容 145,519
关键字:

函数式编程