面向函数范式编程

函数编程(简称FP)不只代指Haskell Scala等之类的语言,还表示一种编程思维,软件思考方式,也称面向函数编程。 编程的本质是组合,组合的本质是范畴Category,而范畴是函数的组合。
 
  首先,什么是函数式编程,这并没有唯一定义,它只是广泛接受的聚合了一些特性的编程风格,我们可以将它与面向对象编程OOP进行对比, 两者区别是,OO主要聚焦于数据的区别,而FP则注重数据结构的一致性。
 
 
  1. 数据和对数据的操作紧紧耦合
  2. 对象隐藏它们操作的实现细节,其他对象调用这些操作只需要通过接口。
  3. .核心抽象模型是数据自己
  4. 核心活动是组合新对象和拓展已经存在的对象,这是通过加入新的方法实现的。
函数编程:
 
  1. 数据与函数是松耦合的
  2. 函数隐藏了它们的实现,语言的抽象是函数,以及将函数组合起来表达。
  3. 核心抽象模型是函数,不是数据结构
  4. 核心活动是编写新的函数。
  5. 变量缺省是不变的,减少可变性变量的使用,并发性好
  那么OOP和FP在业务领域是否有胜者呢? 我们大部分业务逻辑是这样写:
 
 
SELECT orders.order_id, orders.order_date, suppliers.supplier_name
  FROM suppliers
  RIGHT OUTER JOIN orders
  ON suppliers.supplier_id = orders.supplier_id
  WHERE orders.order_status = 'INCOMPLETE'
  ORDER BY orders.order_date DESC;
 
  SQL是非常类似FP,它能渗透到业务中,它使用一致的数据结构(数据表结构Schema),一些基本函数能组合成很多查询语句,它是declarative声明式的, 也就是说,写出的SQL是告诉数据库我需要什么,数据库就为你返回,而不必指定数据库如何具体去查询。
  声明式编程和命令式编程区别? FP的主要特点是它们描述它们要"什么",而不是如何实现。而OO在其方法中,还是使用大部分命令式技术。 下面是命令式技术代码:
 
var sumOfSquares = function(list) {
  var result = 0;
  for (var i = 0; i < list.length; i++) {
    result += square(list[i]);
  }
  return result;
};
console.log(sumOfSquares([2, 3, 5]));
 
函数编程代码如下:
var sumOfSquares = pipe(map(square), reduce(add, 0));
console.log(sumOfSquares([2, 3, 5]));
  函数风格的编程特点:
 
  1. 第一等公民是函数
  2. 带有闭包的Lambdas/Anonymous函数
  3. 大部分无态处理
  4. 无副作用的调用
  5. 通过tail call实现递归的性能优化。
  6. 模式匹配(Haskell, Erlang)
  7. 懒赋值(Miranda, Haskell)
  8. Homoiconicity(类似LISP)
  如果说OOP还有很多人可能受静态数据思路影响,那么FP 带来完全是动态事件,FP让我们直接用动词思考,用方法函数解决问题,比如两个帐号之间的转帐,按照DDD等静态领域建模思维,转帐这个功能是放在帐号这 个实体类中,还是做一个服务呢?在OOP语言中,我们实现功能总是使用服务Service这样一个概念替代,而且强调无态服务,无态服务实际就是一个只有 方法函数没有属性的空架子“类”而已。 2007年的Adam Heroku一篇博文中写道:银行账户之间转帐的老式做法是使用数据库事务,这种做法比较刚性(公牛),正确做法是将转帐事件存储起来,如果你是一个面向函数范式的思维者觉得这样做就很正常。---来自" NOSQL存储的基于事件的事务实现 " 。
 
  有很多人将FP归结于数学思维,实际上这只看到其表面,没有看到数学语言这个背后的形式逻辑,编程语言作为和数学同等形式语言,他们的核心基础都是分析哲学的形式逻辑,过去的面向对象很多设计原则也来源于形式逻辑,见:蒯因与引用透明
 
  函数编程除了无副作用 引用透明等优点以外,而且有着很高的并发性能。
 
  面向对象和面向函数一直在争论,实际上纯粹的OOP和纯粹的FP都是极端的,对于OOP来讲:存在的并一定都是对象,函数就不是对象;对于FP来说:存在的并不总是纯粹的,副作用总是真实存在。
 
  面向对象侧重于分解,函数编程侧重于组合,组合本质是范畴,范畴理论总是鼓励我们从对象内部细节中转移开来,在范畴理论中一个对象 是一个抽象模糊的实体,你所有需要知道的只是它如何和其他对象交互(关系),它是怎么使用箭头和其他对象连接的,这就是为什么Googl等e互联网搜索引 擎能够通过分析链入和链出的链接来排名网站一样(PageRank)。
 
  在面向对象编程中,一个理想化的对象是通过其抽象接口可见的,还包括方法,而方法代表与其他对象的组合依赖,这一刻你得进入对象的内部((方法在对象内部))才能搞清楚它和其他对象如何组合,你就失去了编程范式的优点。
 
 
首先要强调几点:
1. OO不是关于状态
 
对象不是数据结构,对象可能使用数据结构,但是这些数据结构使用方式是隐藏的,这就是为什么数据字段是私有的原因,从对象外部看,你看不见任何状态,所有你看到的都是函数方法,这样对象也是关于函数方法的,不是关于状态。
 
当对象用作数据结构时,它是一种设计风格,比如Hibernate自称自己是对象-关系数据库的映射,其实不正确,ORM并不是关系数据库和对象的映射,而是关系数据库和数据结构的映射,这些数据结构并不是对象。
 
对象是一包函数,不是一包数据。
 
2.函数编程类似OOP是由操作数据的函数组成。
每个函数编程程序都是由操作数据的一系列的函数组成,每个OO程式也是由一系列操作数据的函数组成。
 
对于OO程序员将函数和数据定义在一起,事实上,所有程序都是函数绑定到数据。
 
你也许认为这里绑定的方式还是有区别的,但是仔细想想这是很愚蠢的,难道 f(o), o.f(), 和 (f o)有区别吗?难道区别真的是函数调用语法不同吗?
 
区别
那么OO与 FP的区别是什么?什么是OO有但是FP不能做的,而FP能做的OO则不能的?
 
1. FP规定了分配的纪律
真正函数编程并没有分配符号,你不能改变变量的状态,确实,变量这个词语在函数编程中属于用词不当,你其实不能改变它们。
 
函数编程经常会说函数是第一等公民,smalltalk确实是将函数作为一等公民,但是small talk是面向对象语言,不是函数语言。
 
函数语言与非函数语言的区别是函数语言并不分配statement。
 
这意味着在函数语言中从来不能改变状态吗?不是,函数语言通常提供某种方式让你改变状态, F#允许你定于“可变变量”,Clojure允许你创建一个特殊对象,其状态可以通过魔术的咒语来改变。
 
关键是一种函数式语言对变化状态通过强加某种仪式施以纪律。 你必须通过正确的方式去做。
 
2. OO强加函数指针的约束
一些人认为对象是真实世界的对象映射,这样OOP更加贴近我们的思考方式,其实OO真正特点是使用方便的多态性替代了函数指针。
 
如何实现多态?使用函数指针实现, OO语言简化这种实现,隐藏了函数指针,因为函数指针难以管理得很好,因此这点无疑是值得肯定的,试图使用函数指针如C编写一个多多态代码看看?它们会有复杂不方便的约定,需要遵循“每种情况下”,这通常不现实的。
 
在Java中,你调用的每个函数都是多态的,你没有办法调用不是多态的函数,那就意味着每个java函数间接地通过函数指针被调用。
 
如果你需要在C中使用多态,你得自己管理这些指针,这很难,如果你想在Lisp中使用多态性,你也得自己管理指针,将它们作为参数传送到高阶算法(这是一种策略模式),但是在OO语言中,这些指针语言已经帮助你管理,语言会初始化它们 转换marshal 它们 通过它们调用函数。
 
OO和FP是相互排斥的?
这两者排斥吗?你能有一种语言既能约束变量再分配和函数指针吗?当然,这两者并不是互相排斥的,你可以编写OO-functional程序。
 
那是否意味着所有OO程序员的设计原则和设计模式都可以被函数程序员使用呢?如果他们确实接受面向对象在函数指针上的约束他们就会。
 
但是函数编程者为什么要这样做?有什么好处?他们会得到类似OO程序员约束分配的好处吗?
 
多态好处
多态只有一个好处,而且很大,能够将源码和运行的依赖进行反转。
 
大 多数系统一个函数调用另外一个函数,运行和源码都会发生相同方向的依赖,调用模块依赖于被调用模块,而多态被注射入于两者,那么就存在一个源码依赖的反 转,当然运行时刻调用模块还是依赖被调用模块(源码阶段不是,是通过反转或DI依赖注入将依赖的被调用模块注入到调用模块中),源码阶段的调用模块就再也 不依赖被调用的源码了,而两者只依赖一个多态接口。
 
这种反转允许被调用模块扮演一种类似插件作用,确实,这是也是插件工作原理。
 
插件架构是健壮的,因为稳定的高价值业务规则能够不再依赖于低价值易变化的模块,比如用户接口和数据库(用户接口是高价值,数据库易于变化)
 
最终的结果是,为了是健壮的系统在重大架构内必须使用多态性。
 
不变性的好处
不再重新分配变量值的好处是:如果你从来不更新就不会有并发更新会发生的问题。
 
因为函数编程不再做变量重新分配,可变性被系统特殊仪式照顾,只是预留给非常需要的时候使用。从多个线程和多核角度看这样做本质上是安全的。
 
函数编程的底线是多处理和多核环境中更加安全。
 
深层哲学
当然,面向对象和函数式编程信徒们都将抗议我这种简化分析。 他们会认为,有深厚的哲学区别,以及包括心理和数学原因是他们最喜欢的理由。
 
我的反应是: 呸!
 
每个人都认为他们是最好的方式。 每个人都是错误的。
 
设计原则和设计模式?
前面谈到了有人认为几十年以来设计原则和设计模式仅仅适用OO,而函数编程将它们减少到:函数。
 
这个想法是极端的疯狂。 不管你的编程风格软件设计的原则仍然适用,。 你已经决定使用一种没有一个赋值运算符的语言并不意味着可以忽略单一责任原则;或自动开闭原则。 策略模式对多态性的利用并不意味着该模式不能用于函数式语言。
 
底线就在这里。 面向对象编程是好的,只有当你知道它是什么。 函数式编程是好的,也只有当你知道它是什么。 函数风格的OO编程也是很好的,一旦你知道它是什么。
 
 
 
 
      
posted @ 2017-06-11 22:42  会说话的帆船  阅读(303)  评论(0编辑  收藏  举报