谈谈 JavaScript 函数式编程(未完待续)

285 阅读4分钟

一、范畴轮

  • 函数式编程范畴论的数学分支,是一门很复杂的数学,认为世界上所有概念体系都可以抽象出一个个范畴。
  • 彼此之间存在某种关系概念、事物、对象等等,都构成范畴。任何事物只要找出他们之间的关系,就能定义 。
  • 箭头表示范畴成员之间的关系,正式的名称叫做“态射”(morphism)。范畴论认为,同一个范畴的所有成员,就是不同状态的“变形”(transformation)。通过“态射”,一个成员可以变形成另一个成员。

二、函数式编程基本理论

  • 函数式编程其实相对于计算机的历史而言是一个非常古老的概念,甚至早于第一台计算机的诞生。函数式编程的基础模型来源于 λ (Lambda x=>x*2)演算,而 λ 演算并非设计于在计算机上执行,它是在 20 世纪三十年代引入的一套用于研究函数定义、函数应用和递归的形式系统
  • 函数式编程不是用函数来编程,也不是传统的面向过程编程。主旨在于将复杂的函数符合成简单的函数,运算过程尽量写成一系列嵌套的函数调用
  • JavaScript 是披着 C 外衣的 Lisp。

阮一峰大神的 Lisp 语言简述

Common Lisp的写法如下:

(defun foo (n)
    (lambda (i) (incf n i)))

为啥说 JavaScript 是披着 C 外衣的 Lisp

function foo (n) {
    return function (i) {
        return n += i } }

可怜的 Java(8之前)只能给一个近似的解(还只支持整数)

public interface Inttoint {
    public int call (int i);
}
public static Inttoint foo (final int n) {
    return new Inttoint () {
        int s = n;
        public int call (int i) { 
            s = s + i;
            return s;
        }
    };
}

三、专业术语

纯函数

对于相同的输入,永远会得到相同的输出,而且没有任何可观察的副作用,也不依赖外部环境的状态。

var xs = [1,2,3,4,5];
// Array.slice是纯函数,因为它没有副作用,对于固定的输入,输出总是固定的
xs.slice(0,3);
xs.slice(0,3); // 没改
xs.splice(0,3); 
xs.splice(0,3); // 改了

纯函数不仅可以有效降低系统的复杂度,还有很多很棒的特性,比如可缓存性。

比如:

import _ from 'lodash'; 
var sin = _.memorize(x => 
Math.sin(x)); 
//第一次计算的时候会稍慢一点 
var a = sin(1); 
//第二次有了缓存,速度极快 
var b = sin(1);

不纯的话:

//不纯的 
var min = 18; 
var checkage = age => age > min; 
//纯的,这很函数式 
var checkage = age => age > 18;

不纯的版本中,checkage 不仅取决于 age 还有外部依赖的变量 min。纯的版本中 checkage 把关键数字 18 硬编码在函数内部,扩展性比较差。

那么,我们怎么解决这个问题呢?

柯里化

我们可以把 min 也从外部传入

function fun(min) {
    return function (age) {
        return min > age;
    }
}

fun(10)(18);

简写为

const fun = min => age => age > min;

事实上柯里化是一种“预加载”函数的方法,通过传递较少的参数,得到一个已经记住了这些参数的新函数,某种意义上讲,这是一种对参数的“缓存”,是一种非常高效的编写函数的方法。

在写纯函数的过程中,我们很容易写出柯里化的洋葱代码h(g(f(x))),为了解决函数嵌套的问题,我们需要用到函数组合

函数组合以及 Point Free 风格

Point Free 风格 把一些对象自带的方法转化成纯函数,不要命名转瞬即逝的中间变量。

const fun = str => str.toUpperCase().split(" ");

fun("hello world!"); // [ 'HELLO', 'WORLD!' ]

在这个函数中,我们使用了 str 作为我们的中间变量,但这个中间变量除了让代码变得长了一点以外是毫无意义的。

我们改为柯里化:

const split = str => word => word.split(str);
const upper = word => word.toUpperCase();

const res=split(" ")(upper("hello world!"));

避免出现洋葱代码,我们可以改为Point Free风格

const compose = (f, g) => x => f(g(x));

const split = str => word => word.split(str);
const upper = word => word.toUpperCase();

const fun = compose(split(" "), upper);
const res = fun("hello world!");

console.log(res);

未完待续