架构师进阶之二函数式编程和面向对象编程
发布日期:2021-06-30 16:21:40 浏览次数:2 分类:技术文章

本文共 8725 字,大约阅读时间需要 29 分钟。

什么是函数式编程

简单来说:面向对象编程各个模块之间相互依赖,好比流水线生产模式,互相依赖状态,共享数据等等。而FP编程则互相独立,没有任何依赖

因此:FP编程,模块化程度更高,更容易测试,代码也更简洁,比如下面数组遍历的例子。因为模块化程度高,所以FP编程往往是声明式编程,调用现有的函数,你只是需要声明你要做什么,不需要编写具体的step代码。

由于FP编程模块化程度高,所以适用于并行编程和异步编程。

原文:

Functional programming is a declarative paradigm, meaning that the program logic is expressed without explicitly describing the flow control.

Imperative programs spend lines of code describing the specific steps used to achieve the desired results — the flow control: How to do things.

Declarative programs abstract the flow control process, and instead spend lines of code describing the data flow: What to do. The how gets abstracted away.

For example, this imperative mapping takes an array of numbers and returns a new array with each number multiplied by 2:

const doubleMap = numbers => {  const doubled = [];  for (let i = 0; i < numbers.length; i++) {    doubled.push(numbers[i] * 2);  }  return doubled;};console.log(doubleMap([2, 3, 4])); // [4, 6, 8]

This declarative mapping does the same thing, but abstracts the flow control away using the functional Array.prototype.map() utility, which allows you to more clearly express the flow of data:

const doubleMap = numbers => numbers.map(n => n * 2);console.log(doubleMap([2, 3, 4])); // [4, 6, 8]

Imperative code frequently utilizes statements. A statement is a piece of code which performs some action. Examples of commonly used statements include forifswitchthrow, etc…

Declarative code relies more on expressions. An expression is a piece of code which evaluates to some value. Expressions are usually some combination of function calls, values, and operators which are evaluated to produce the resulting value.

函数编程语言

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

函数合成

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

柯里化

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

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

闭包

Variable scope

When you declare a local variable, that variable has a scope. Generally, local variables exist only within the block or function in which you declare them.

function() {  var a = 1;  console.log(a); // works}    console.log(a); // fails

If I try to access a local variable, most languages will look for it in the current scope, then up through the parent scopes until they reach the root scope.

var a = 1;function() {  console.log(a); // works}    console.log(a); // works

When a block or function is done with, its local variables are no longer needed and are usually blown out of memory.

This is how we normally expect things to work.

A closure is a persistent local variable scope

A closure is a persistent scope which holds on to local variables even after the code execution has moved out of that block. Languages which support closure (such as JavaScript, Swift, and Ruby) will allow you to keep a reference to a scope (including its parent scopes), even after the block in which those variables were declared has finished executing, provided you keep a reference to that block or function somewhere.

The scope object and all its local variables are tied to the function and will persist as long as that function persists.

This gives us function portability. We can expect any variables that were in scope when the function was first defined to still be in scope when we later call the function, even if we call the function in a completely different context.

For example

Here's a really simple example in JavaScript that illustrates the point:

outer = function() {  var a = 1;  var inner = function() {    console.log(a);  }  return inner; // this returns a function}var fnc = outer(); // execute outer to get inner fnc();

Here I have defined a function within a function. The inner function gains access to all the outer function's local variables, including a. The variable a is in scope for the inner function.

Normally when a function exits, all its local variables are blown away. However, if we return the inner function and assign it to a variable fnc so that it persists after outer has exited, all of the variables that were in scope when inner was defined also persist. The variable a has been closed over -- it is within a closure.

Note that the variable a is totally private to fnc. This is a way of creating private variables in a functional programming language such as JavaScript.

As you might be able to guess, when I call fnc() it prints the value of a, which is "1".

In a language without closure, the variable a would have been garbage collected and thrown away when the function outer exited. Calling fnc would have thrown an error because a no longer exists.

In JavaScript, the variable a persists because the variable scope is created when the function is first declared and persists for as long as the function continues to exist.

a belongs to the scope of outer. The scope of inner has a parent pointer to the scope of outerfnc is a variable which points to innera persists as long as fnc persists. a is within the closure.

JDK1.8 lambda

 是一流的面向对象语言,除了部分简单数据类型,Java 中的一切都是对象,即使数组也是一种对象,每个类创建的实例也是对象。在 Java 中定义的函数或方法不可能完全独立,也不能将方法作为参数或返回一个方法给实例。

从 Swing 开始,我们总是通过匿名类给方法传递函数功能,以下是旧版的事件监听代码:

someObject.addMouseListener(new MouseAdapter() {        public void mouseClicked(MouseEvent e) {            //Event listener implementation goes here...        }    });

在上面的例子里,为了给 Mouse 监听器添加自定义代码,我们定义了一个匿名内部类 MouseAdapter 并创建了它的对象,通过这种方式,我们将一些函数功能传给 addMouseListener 方法。

简而言之,在 Java 里将普通的方法或函数像参数一样传值并不简单,为此,Java 8 增加了一个语言级的新特性,名为 Lambda 表达式

在函数式编程语言中,函数是一等公民,它们可以独立存在,你可以将其赋值给一个变量,或将他们当做参数传给其他函数。JavaScript 是最典型的函数式编程语言。通过下面oracle官方文档能看出,java引入lamada表达式就是为了将函数当成参数传递。

One issue with anonymous classes is that if the implementation of your anonymous class is very simple, such as an interface that contains only one method, then the syntax of anonymous classes may seem unwieldy and unclear. In these cases, you're usually trying to pass functionality as an argument to another method, such as what action should be taken when someone clicks a button. Lambda expressions enable you to do this, to treat functionality as method argument, or code as data.

The previous section, , shows you how to implement a base class without giving it a name. Although this is often more concise than a named class, for classes with only one method, even an anonymous class seems a bit excessive and cumbersome. Lambda expressions let you express instances of single-method classes more compactly.

看看我们常用的foreach代码,foreach的入参是一个函数接口,@FunctionalInterface,我们把这个函数的作为参数传入foreach,这就是java函数式编程的体现。我们可以自己实现consumer函数接口,然后传入foreach,但是这样不够简洁,为了避免匿名内部类,java为我们提供了简洁的方式---lamaba表达式。

We use forEach to iterate over a collection and perform a certain action on each element. The action to be performed is contained in a class that implements the Consumer interface and is passed to forEach as an argument.

The Consumer interface is  (an interface with a single abstract method). It accepts an input and returns no result.

Here’s the definition:

1

2

3

4

@FunctionalInterface

public interface Consumer {

    void accept(T t);

}

Therefore, any implementation, for instance, a consumer that simply prints a String:

1

2

3

4

5

Consumer<String> printConsumer = new Consumer<String>() {

    public void accept(String name) {

        System.out.println(name);

    };

};

can be passed to forEach as an argument:

1

names.forEach(printConsumer);

But that is not the only way of creating an action via a consumer and using forEach API.

Let’s see the 3 most popular ways in which we will use the forEach method:

 

JDK 1.8 Stream

为什么需要 Stream

Stream作为 Java 8的一大亮点,它与 java.io 包里的 InputStream和 OutputStream是完全不同的概念。它也不同于 StAX 对 XML 解析的 Stream,也不是 Amazon Kinesis 对大数据实时处理的Stream。Java 8 中的 Stream 是对集合(Collection)对象功能的增强,它专注于对集合对象进行各种非常便利、高效的聚合操作(aggregate operation),或者大批量数据操作 (bulk data operation)。Stream API 借助于同样新出现的Lambda表达式,极大的提高编程效率和程序可读性。同时它提供串行和并行两种模式进行汇聚操作,并发模式能够充分利用多核处理器的优势,使用fork/join并行方式来拆分任务和加速处理过程。通常编写并行代码很难而且容易出错, 但使用Stream API无需编写一行多线程的代码,就可以很方便地写出高性能的并发程序。所以说,Java 8 中首次出现的java.util.stream 是一个函数式语言+多核时代综合影响的产物。

下面的代码进行了1.7和1.8 stream操作的代码区别:

List
groceryTransactions = new Arraylist<>();for(Transaction t: transactions){ if(t.getType() == Transaction.GROCERY){ groceryTransactions.add(t); }}Collections.sort(groceryTransactions, new Comparator(){ public int compare(Transaction t1, Transaction t2){ return t2.getValue().compareTo(t1.getValue()); }});List
transactionIds = new ArrayList<>();for(Transaction t: groceryTransactions){ transactionsIds.add(t.getId());}
List
transactionsIds = transactions.parallelStream(). filter(t -> t.getType() == Transaction.GROCERY). sorted(comparing(Transaction::getValue).reversed()). map(Transaction::getId). collect(toList());

 

转载地址:https://kerry.blog.csdn.net/article/details/87785079 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:架构师进阶之三Zookeeper,Mesos以及Marathon
下一篇:mCAS之VIP

发表评论

最新留言

能坚持,总会有不一样的收获!
[***.219.124.196]2024年05月01日 01时44分48秒