从实现一个红绿灯到函数式编程

我认为函数式编程的本质是把函数当作变量来使用。最近接触了React,我们可以发现在React中处处存在函数式编程的思想,我们将JSX写成一个函数的返回值,并且在render中调用她们。函数是对于过程的抽象,我们将过程抽象成一个函数,然后根据不同的场景传入不同的变量值从而得到不同的结果。而函数式编程是对于函数的抽象,我们把函数看作是一个变量,然后根据不同的场景传入不同的函数值从而得到不同的结果。

我希望可以通过实现一个小的demo来讲清楚这件事,这个demo是我很久以前在某个技术网站看到的,具体的网址已经记不清楚了,鉴于小伙伴们对于我所讲的函数式编程的概念非常感兴趣,所以我将这个例子翻了出来,自己将它重新实现一遍。若是没有讲清楚或错误的地方,则是受限于我的浅薄的技术功底,并希望被指正。

红绿灯函数

客户希望我们实现一个红绿灯函数,每隔1秒钟,变换一次信号,无限循环。可以使用console.log来模拟信号灯亮的过程,即在控制台打印'绿灯亮',1s后继续打印'黄灯亮',1s后打印'红灯亮'...无限循环

实现这个效果非常简单,直接上代码

function semaphores() {
    setTimeout(()=>{
        console.log('绿灯亮');
        setTimeout(() => {
            console.log('黄灯亮');
            setTimeout(() => {
                console.log('红灯亮');
                semaphores();
            }, 1000)
        }, 1000)
    },1000) 
}

// delay 1s
// 绿灯亮 
// delay 1s
// 黄灯亮
// delay 1s
// 红灯亮
// delay 1s
// ...

但是很明显,这样的函数及其不优雅,我们的红绿灯只有三种颜色,如果要是有十几种颜色的霓虹灯闪来闪去的话,我们就要嵌套十几层代码了。这样可读性实在是太差了,于是我们希望可以把它优化一下。

仔细分析一下我们的代码就会发现,上面的demo有非常多的冗余代码,setTimeout在一个函数中被重复使用了三次,每次只不过是一个嵌套再打印不同的红黄绿灯而已,我们为什么不把公共部分提取出来呢?把红黄绿灯提取成变量,代码质量会好很多。

so出现了下列的代码:

const TRAFFIC_LIGHT = [
    '绿灯',
    '黄灯',
    '红灯'
]

function semaphores(lightList = [], count = 0) {
    // 控制器
    let lightLength = lightList.length;
    
    return setTimeout(() => {
        console.log(`${lightList[count % lightLength]}`);
        count ++;
        semaphores(lightList,count);
    }, 1000)
}

// delay 1s
// 绿灯亮 
// delay 1s
// 黄灯亮
// delay 1s
// 红灯亮
// delay 1s
// ...

我们还可以进一步优化,同时将时间抽离:

const TRAFFIC_LIGHT = [
    ['绿灯', 1000],
    ['黄灯', 2000],
    ['红灯', 3000]
]

function semaphores(lightList = [], count = 0) {
    const lightLength = lightList.length;     
    
    return function handleSemaphores(){
        const [light, time] = lightList[count % lightLength];
              
        return setTimeout(() => {
            console.log(`${light}亮`);
            count ++;
            handleSemaphores();
        }, time)
    }
}

// delay 1s
// 绿灯亮 
// delay 2s
// 黄灯亮
// delay 3s
// 红灯亮
// delay 1s
// ...

我们可以更加容易修改我们的代码,以应对将来可能的需求变更。这也是我们常见的函数形式。变量只是 Number、String、Object、undefined、null、Boolean,但是我们再次将问题变得复杂一些:

我们的红绿灯效果良好,客户非常满意,并且希望扩展应用范围。为了应对可能到来的困惑,我们需要在每次信号灯亮起的时候添加上一条提示语,提醒剩余时间。不同的应用场景将有不同的提示语。

这也非常简单,我们只需要多加一条语句就可以完成:

const TRAFFIC_LIGHT = [
    ['绿灯', 1000],
    ['黄灯', 2000],
    ['红灯', 3000]
]

function semaphores(lightList = [], count = 0) {
    const lightLength = lightList.length;     
    
    return function handleSemaphores(){
        const [light, time] = lightList[count % lightLength];
              
        return setTimeout(() => {
            console.log(`${light}亮`);
            // add here!!!
            console.log('这是一条提示语');
            count ++;
            handleSemaphores();
        }, time)
    }
}

// delay 1s
// 绿灯亮 
// 这是一条提示语
// delay 2s
// 黄灯亮
// 这是一条提示语
// delay 3s
// 红灯亮
// 这是一条提示语
// delay 1s
// ...

但随着我们的应用场景进一步增多,我们的红绿灯将广泛的用于医疗、运输、零售行业,对于这些行业我们需要在信号灯亮起时拥有不同的操作,有的是发出声响、有的是打印提示语、有的是发出警报、有的是打开开关...

那么我们是不是需要把上面的那份代码到处拷贝一下,然后修改位于16行的代码,分别把它修改为发声、打印、报警、开关控制...

显然一般的函数已经无法满足我们的需求了,这时候我们就需要函数式编程。

函数式编程

我上面只是为了引出函数式编程的例子,所以举例夸张了一些。事实上函数式编程并不是非常高大上的概念,它就和面向对象、面向过程一样,只是一种编程的范式,在实际中拥有着非常广泛的应用(例如JSX)

就像我一开始提到的那样,函数式编程的核心不过是将函数当作变量那样来使用。我们希望改造一下我们的红绿灯函数,使我们的函数在信号灯亮后,可以在不同的场景使用不同的参数,我们来设计一下这个函数.首先我们需要传入一个函数,所以我们在形参列表中加入一个handler的变量,便于我们使用它。然后在我们希望使用它的地方使用就好了。

const TRAFFIC_LIGHT = [
    ['绿灯', 1000],
    ['黄灯', 2000],
    ['红灯', 3000]
]

// 请注意我们在这里加入了一个参数handler
function semaphores(lightList = [], handler, count = 0) {
    const lightLength = lightList.length;     
    
    return function handleSemaphores(){
        const [light, time] = lightList[count % lightLength];
              
        return setTimeout(() => {
            console.log(`${light}亮`);
            // 在这里使用它
            handler && handler(light, time);
            count ++;
            handleSemaphores();
        }, time)
    }
}

我们可以这样使用上面的函数:

// 我们只需要在不同的场景改变不同的handler就可以了
const handler = function (light, time) {
    console.log('函数式编程好爽啊');
    console.log(`${light}亮${time}毫秒~~`)
}

const doTrafficLight = semaphores(TRAFFIC_LIGHT, handler);
const trafficTimeHandler = doTrafficLight();
// delay 1s
// 绿灯亮
// 函数式编程好爽啊
// 绿灯亮1000毫秒~~
// delay 2s
// 黄灯亮
// 函数式编程好爽啊
// 黄灯亮2000毫秒~~
// delay 3s
// 红灯亮
// 函数式编程好爽啊
// 红灯亮3000毫秒~~
// ...

我们更可以优化一下上面的函数,使得不同等亮起时使用不同的行为函数:

const TRAFFIC_LIGHT = [
    ['绿灯', 1000, function() {
     // do something...
    }],
    ['黄灯', 2000, function() {
     // do something...
    }],
    ['红灯', 3000, function() {
     // do something...
    }]
]

// 我们去掉了handler!!!!
function semaphores(lightList = [], count = 0) {
    const lightLength = lightList.length;     
    
    return function handleSemaphores(){
        const [light, time, handler] = lightList[count % lightLength];
              
        return setTimeout(() => {
            console.log(`${light}亮`);
            // 在这里使用它
            handler && handler(light, time);
            count ++;
            handleSemaphores();
        }, time)
    }
}

// delay 1s
// 绿灯亮
// dosomething1...
// delay 2s
// 黄灯亮
// dosomething2...
// delay 3s
// 红灯亮
// dosomething3...
// ...

函数式编程的抽象程度更高,它是对于过程的抽象。如果没有传入的参数作为支撑,那么这个函数毫无意义,比较明显的例子是JavaScript中的mapreduce。这是最主要的概念,剩下的概念则都是一些规定,帮助我们写出更好的更健壮的函数式函数,相信理解了我刚刚所说的例子,再去理解其他概念会容易许多。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 221,695评论 6 515
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,569评论 3 399
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 168,130评论 0 360
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,648评论 1 297
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,655评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,268评论 1 309
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,835评论 3 421
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,740评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,286评论 1 318
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,375评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,505评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,185评论 5 350
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,873评论 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,357评论 0 24
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,466评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,921评论 3 376
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,515评论 2 359