# 函数式编程

# 1 定义

# 不同的想法

  • 1 函数式编程关心数据的映射,命令式编程关心解决问题的步骤

  • 2 函数式编程关心类型(代数结构)之间的关系,命令式编程关心解决问题的步骤

  • 3 命令式编程是面向计算机硬件的抽象,有变量(对应着存储单元),赋值语句(获取,存储指令)表达式(内存引用和算术运算)和控制语句(跳转指令),一句话,命令式程序就是一个冯诺依曼机指令序列

  • 而函数式编程是面向数学的抽象,将计算描述为一种表达式求值,一句话,函数式程序就是一个表达式.

  • 函数式编程中的函数这个术语不是指计算机中的函数(实际上是Subroutine),而是指数学中的函数,即自变量的映射。就是说一个函数的值仅决定于函数参数的值,不依赖其他状态。比如sqrt(x)函数计算x的平方根,只要x不变,不论什么时候调用,调用几次,值都是不变的。

模块复用和访存控制。         
1. 模块复用“通过少量的表达式构造规则和灵活的表达式组合方式,可以构造足够实用、强大的程序语言。
”变量、函数、对象、类型等等都是可以复用的程序元素。
第一阶函数:函数可以被赋给变量,可以作为其他函数的参数、返回值。函数的复用更自由、方便。


// JavaScript
function outer() {
    var x = 11;
    function inner() {
        x = x + 1;
        console.log(x);
    }

    return inner; // 函数作为返回值
}

// 函数赋给变量 
var test = outer();


抽象数据类型:在处理递归结构时方便很多(如数据结构中的树,编译器的Parser等)
以TAPL第三章untyped arith语言的解释器为例(部分)(* Ocaml *)
type term =
    TmTrue of info 
  | TmFalse of info 
  | TmIf of info * term * term * term 
  | TmSucc of info * term
  | TmPred of info * term 
  | TmIsZero of info * term // Scala      
// case class/object可以实现ADT
sealed trait Term         
case object TmTrue extends Term 
case object TmFalse extends Term
case class TmIf(cond: Term, t1: Term, t2: Term) extends Term
case object TmZero extends Term 
case class TmPred(t: Term) extends Term 
case class TmIsZero(t: Term) extends Term在ML、Haskell等语言中,
递归函数、数据结构的定义往往会用到模式匹配 语法模式匹配通常比if-else和switch更强大、高效。
(注:初学者在使用时常犯错误多样化的多态函数重载(ad-hoc多态)、泛型(参数化多态)和subtyping。
Haskell的typeclass,Ocaml的Module system,Common lisp的mutiple dispatch。。。
当然,C++的模板+模板特化可以模拟typeclass,Java不也有泛型么(黑)。。


2. 访存控制 变量的不可变性"Aliasing+mutation=>内存问题,aliasing+mutation+ordering=>数据竞争。
想要安全,就应该要么消除aliasing,要么消除mutation。"纯函数式语言如Haskell,
号称“变量的不可变性”(immutable),即只绑定一次,完全无副作用。
这种理念影响了Scala、Rust等现代语言如Scala中val和var的区别,
标准库分别提供immutable和mutable collections。
类型相关的权限dynamic type一时爽。
当然Scheme/Clojure等语言也是动态类型的。。这里要强调的是静态强类型。在函数式语言中它得到了充分的发展。
前面讨论的内容可能并不限于“函数式”,也必然有些内容未涉及。
待补充。。程序复用和权限控制,在命令式、面向对象中也有,
但是函数式语言中它们做得更极致,或者说,给了人们新的视角:为什么一定要把函数放在类中(Java),
为什么要函数指针用多了那么别扭(C),
为什么有些设计模式实现起来那么地“绕”,为什么不能摆脱和锁的斗争。。

# 什么是函数式编程

函数式编程是一种编程范式,主要是利用函数把运算过程封装起来,通过组合各种函数来计算结果。函数式编程意味着你可以在更短的时间内编写具有更少错误的代码。举个简单的例子,假设我们要把字符串 functional programming is great 变成每个单词首字母大写,我们可以这样实现:

var string = 'functional programming is great';
var result = string
  .split(' ')
  .map(v => v.slice(0, 1).toUpperCase() + v.slice(1))
  .join(' ');

上面的例子先用 split 把字符串转换数组,然后再通过 map 把各元素的首字母转换成大写,最后通过 join 把数组转换成字符串。 整个过程就是 join(map(split(str))),体现了函数式编程的核心思想: 通过函数对数据进行转换

由此我们可以得到,函数式编程有两个基本特点:

  • 通过函数来对数据进行转换
  • 通过串联多个函数来求结果

# 2 好处

# 1 无副作用

指调用函数时不会修改外部状态,即一个函数调用 n 次后依然返回同样的结果。

var a = 1;
// 含有副作用,它修改了外部变量 a
// 多次调用结果不一样
function test1() {
  a++
  return a;
}
// 无副作用,没有修改外部状态
// 多次调用结果一样
function test2(a) {
  return a + 1;
}

#

# 2 透明引用

指一个函数只会用到传递给它的变量以及自己内部创建的变量,不会使用到其他变量。

var a = 1;
var b = 2;
// 函数内部使用的变量并不属于它的作用域
function test1() {
  return a + b;
}
// 函数内部使用的变量是显式传递进去的
function test2(a, b) {
  return a + b;
}

#

#

# 3不可变变量

  • 由于(多个线程之间)不共享状态,不会造成资源争用(Race condition),也就不需要用来保护可变状态,也就不会出现死锁
  • 这样可以更好地并发起来,尤其是在对称多处理器(SMP)架构下能够更好地利用多个处理器(核)提供的并行处理能力

指的是一个变量一旦创建后,就不能再进行修改,任何修改都会生成一个新的变量。使用不可变变量最大的好处是线程安全。多个线程可以同时访问同一个不可变变量,让并行变得更容易实现。 由于 JavaScript 原生不支持不可变变量,需要通过第三方库来实现。 (如 Immutable.js,Mori 等等)

var obj = Immutable({ a: 1 });
var obj2 = obj.set('a', 2);
console.log(obj);  // Immutable({ a: 1 })
console.log(obj2); // Immutable({ a: 2 })

# 4 新特性

  • 高阶函数(Higher-order function)
  • 偏应用函数(Partially Applied Functions)
  • 柯里化(Currying)
  • 闭包(Closure)