百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程字典 > 正文

前端必学——函数式编程(三)(前端函数式编程理解)

toyiye 2024-08-22 23:09 7 浏览 0 评论

在前面的文章,我们谈了基础之基础,重要之重要——“偏函数”,偏函数通过函数封装,实现了减少传参数量的目的,解决了手动指定实参的麻烦。

更具重要意义的是:

当函数只有一个形参时,我们能够比较容易地组合它们。这种单元函数,便于进行后续的组合函数;

没错,本篇就是谈关于 “组合函数”。它是函数编程的重中之重之重之重重重!

组合函数

含义

函数编程就像拼乐高!

乐高有各式各样的零部件,我们将它们组装拼接,拼成一个更大的组件或模型。

函数编程也有各种功能的函数,我们将它们组装拼接,用于实现某个特定的功能。

下面来看一个例子,比如我们要使用这两个函数来分析文本字符串:

function words(str) {
    return String( str )
        .toLowerCase()
        .split( /\s|\b/ )
        .filter( function alpha(v){
            return /^[\w]+$/.test( v );
        } );
}

function unique(list) {
    var uniqList = [];

    for (let i = 0; i < list.length; i++) {
        if (uniqList.indexOf( list[i] ) === -1 ) {
            uniqList.push( list[i] );
        }
    }

    return uniqList;
}

var text = "To compose two functions together";

var wordsFound = words( text );
var wordsUsed = unique( wordsFound );

wordsUsed;

//  ["to", "compose", "two", "functions", "together"]

不用细看,只用知道:我们先用 words 函数处理了 text,然后用 unique 函数处理了上一处理的结果 wordsFound;

这样的过程就好比生产线上加工商品,流水线加工。


想象一下,如果你是工厂老板,还会怎样优化流程、节约成本?

这里作者给了一种解决方式:去掉传送带!

即减少中间变量,我们可以这样调用:

var wordsUsed = unique( words( text ) );

wordsUsed

确实,少了中间变量,更加清晰,还能再优化吗?

我们还可以进一步把整个处理流程封装到一个函数内:

function uniqueWords(str) {
    return unique( words( str ) );
}

uniqueWords(text)

这样就像是一个黑盒,无需管里面的流程,只用知道这个盒子输入是什么!输出是什么!输入输出清晰,功能清晰,非常“干净”!如图:

与此同时,它还能被搬来搬去,或再继续组装。

我们回到 uniqueWords() 函数的内部,它的数据流也是清晰的:

uniqueWords <-- unique <-- words <-- text

封装盒子

上面的封装 uniqueWords 盒子很 nice ,如果要不断的封装像 uniqueWords 的盒子,我们要一个一个的去写吗?

function uniqueWords(str) {
    return unique( words( str ) );
}

function uniqueWords_A(str) {
    return unique_A( words_A( str ) );
}

function uniqueWords_B(str) {
    return unique_B( words_B( str ) );
}

...

所以,一切为了偷懒,我们可以写一个功能更加强大的函数来实现自动封装盒子:

function compose2(fn2,fn1) {
    return function composed(origValue){
        return fn2( fn1( origValue ) );
    };
}

// ES6 箭头函数形式写法
var compose2 =
    (fn2,fn1) =>
        origValue =>
            fn2( fn1( origValue ) );

接着,调用就变成了这样:

var uniqueWords = compose2( unique, words );

var uniqueWords_A = compose2( unique_A, words_A );

var uniqueWords_B = compose2( unique_B, words_B );

太清晰了!

任意组合

上面,我们组合了两个函数,实际上我们也可以组合 N 个函数;

finalValue <-- func1 <-- func2 <-- ... <-- funcN <-- origValue

比如用一个 compose 函数来实现(敲重点):

function compose(...fns) {
    return function composed(result){
        // 拷贝一份保存函数的数组
        var list = fns.slice();

        while (list.length > 0) {
            // 将最后一个函数从列表尾部拿出
            // 并执行它
            result = list.pop()( result );
        }

        return result;
    };
}

// ES6 箭头函数形式写法
var compose =
    (...fns) =>
        result => {
            var list = fns.slice();

            while (list.length > 0) {
                // 将最后一个函数从列表尾部拿出
                // 并执行它
                result = list.pop()( result );
            }

            return result;
        };

基于前面 uniqueWords(..) 的例子,我们进一步再增加一个函数来处理(过滤掉长度小于等于4的字符串):

function skipShortWords(list) {
    var filteredList = [];

    for (let i = 0; i < list.length; i++) {
        if (list[i].length > 4) {
            filteredList.push( list[i] );
        }
    }

    return filteredList;
}

var text = "To compose two functions together";

var biggerWords = compose( skipShortWords, unique, words );

var wordsUsed = biggerWords( text );

wordsUsed;
// ["compose", "functions", "together"]

这样 compose 函数就有三个入参且都是函数了。我们还可以利用偏函数的特性实现更多:

function skipLongWords(list) { /* .. */ }

var filterWords = partialRight( compose, unique, words ); // 固定 unique 函数 和 words 函数

var biggerWords = filterWords( skipShortWords );
var shorterWords = filterWords( skipLongWords );

biggerWords( text );
shorterWords( text );

filterWords 函数是一个更具有特定功能的变体(根据第一个函数的功能来过滤字符串)。

compose 变体

compose(..)函数非常重要,但我们可能不会在生产中使用自己写的 compose(..),而更倾向于使用某个库所提供的方案。了解其底层工作的原理,对我们强化理解函数式编程也非常有用。

我们理解下 compose(..) 的另一种变体 —— 递归的方式实现:

function compose(...fns) {
    // 拿出最后两个参数
    var [ fn1, fn2, ...rest ] = fns.reverse();

    var composedFn = function composed(...args){
        return fn2( fn1( ...args ) );
    };

    if (rest.length == 0) return composedFn;

    return compose( ...rest.reverse(), composedFn );
}

// ES6 箭头函数形式写法
var compose =
    (...fns) => {
        // 拿出最后两个参数
        var [ fn1, fn2, ...rest ] = fns.reverse();

        var composedFn =
            (...args) =>
                fn2( fn1( ...args ) );

        if (rest.length == 0) return composedFn;

        return compose( ...rest.reverse(), composedFn );
    };

通过递归进行重复的动作比在循环中跟踪运行结果更易懂,这可能需要更多时间去体会;

基于之前的例子,如果我们想让参数反转:

var biggerWords = compose( skipShortWords, unique, words );

// 变成

var biggerWords = pipe( words, unique, skipShortWords );

只需要更改 compose(..) 内部实现这一句就行:

...
        while (list.length > 0) {
            // 从列表中取第一个函数并执行
            result = list.shift()( result );
        }
...

虽然只是颠倒参数顺序,这二者没有本质上的区别。

抽象能力

你是否会疑问:什么情况下可以封装成上述的“盒子”呢?

这就很考验 —— 抽象的能力了!

实际上,有两个或多个任务存在公共部分,我们就可以进行封装了。

比如:

function saveComment(txt) {
    if (txt != "") {
        comments[comments.length] = txt;
    }
}

function trackEvent(evt) {
    if (evt.name !== undefined) {
        events[evt.name] = evt;
    }
}

就可以抽象封装为:

function storeData(store,location,value) {
    store[location] = value;
}

function saveComment(txt) {
    if (txt != "") {
        storeData( comments, comments.length, txt );
    }
}

function trackEvent(evt) {
    if (evt.name !== undefined) {
        storeData( events, evt.name, evt );
    }
}

在做这类抽象时,有一个原则是,通常被称作 DRY(don't repeat yourself),即便我们要花时间做这些非必要的工作。

抽象能让你的代码走得更远! 比如上例,还能进一步升级:

function conditionallyStoreData(store,location,value,checkFn) {
    if (checkFn( value, store, location )) {
        store[location] = value;
    }
}

function notEmpty(val) { return val != ""; }

function isUndefined(val) { return val === undefined; }

function isPropUndefined(val,obj,prop) {
    return isUndefined( obj[prop] );
}

function saveComment(txt) {
    conditionallyStoreData( comments, comments.length, txt, notEmpty );
}

function trackEvent(evt) {
    conditionallyStoreData( events, evt.name, evt, isPropUndefined );
}

这样 if 语句也被抽象封装了。

抽象是一个过程,程序员将一个名字与潜在的复杂程序片段关联起来,这样该名字就能够被认为代表函数的目的,而不是代表函数如何实现的。通过隐藏无关的细节,抽象降低了概念复杂度,让程序员在任意时间都可以集中注意力在程序内容中的可维护子集上。—— 《程序设计语言》

我们在本系列初始提到:“一切为了创造更可读、更易理解的代码。”

从另一个角度,抽象就是将命令式代码变成声命式代码的过程。从“怎么做”转化成“是什么”。

命令式代码主要关心的是描述怎么做来准确完成一项任务。声明式代码则是描述输出应该是什么,并将具体实现交给其它部分。

比如 ES6 增加的结构语法:

function getData() {
    return [1,2,3,4,5];
}

// 命令式
var tmp = getData();
var a = tmp[0];
var b = tmp[3];

// 声明式
var [ a ,,, b ] = getData();

开发者需要对他们程序中每个部分使用恰当的抽象级别保持谨慎,不能太过,也不能不够。

阶段小结

函数组合是为了符合“声明式编程风格”,即关注“是什么”,而非具体“做什么”。

它能将一个函数调用的输出路由跳转到另一个函数的调用上,然后一直进行下去,它借助 compose(..) 或它的变体实现。。

我们期望组合中的函数是一元的(输入输出尽量是一个),这个也是前篇有提到的很重要的一个点。

组合 ———— 声明式数据流 ———— 是支撑函数式编程其他特性的最重要的工具之一!

以上!

我是掘金安东尼: 一名人气前端技术博主(文章 100w+ 阅读量)

终身写作者(INFP 写作人格)

坚持与热爱(简书打卡 1000 日)

我能陪你一起度过漫长技术岁月吗(以梦为马)

觉得不错,给个点赞和关注吧(这是我最大的动力 )b( ̄▽ ̄)d

相关推荐

# Python 3 # Python 3字典Dictionary(1)

Python3字典字典是另一种可变容器模型,且可存储任意类型对象。字典的每个键值(key=>value)对用冒号(:)分割,每个对之间用逗号(,)分割,整个字典包括在花括号({})中,格式如...

Python第八课:数据类型中的字典及其函数与方法

Python3字典字典是另一种可变容器模型,且可存储任意类型对象。字典的每个键值...

Python中字典详解(python 中字典)

字典是Python中使用键进行索引的重要数据结构。它们是无序的项序列(键值对),这意味着顺序不被保留。键是不可变的。与列表一样,字典的值可以保存异构数据,即整数、浮点、字符串、NaN、布尔值、列表、数...

Python3.9又更新了:dict内置新功能,正式版十月见面

机器之心报道参与:一鸣、JaminPython3.8的热乎劲还没过去,Python就又双叒叕要更新了。近日,3.9版本的第四个alpha版已经开源。从文档中,我们可以看到官方透露的对dic...

Python3 基本数据类型详解(python三种基本数据类型)

文章来源:加米谷大数据Python中的变量不需要声明。每个变量在使用前都必须赋值,变量赋值以后该变量才会被创建。在Python中,变量就是变量,它没有类型,我们所说的"类型"是变...

一文掌握Python的字典(python字典用法大全)

字典是Python中最强大、最灵活的内置数据结构之一。它们允许存储键值对,从而实现高效的数据检索、操作和组织。本文深入探讨了字典,涵盖了它们的创建、操作和高级用法,以帮助中级Python开发...

超级完整|Python字典详解(python字典的方法或操作)

一、字典概述01字典的格式Python字典是一种可变容器模型,且可存储任意类型对象,如字符串、数字、元组等其他容器模型。字典的每个键值key=>value对用冒号:分割,每个对之间用逗号,...

Python3.9版本新特性:字典合并操作的详细解读

处于测试阶段的Python3.9版本中有一个新特性:我们在使用Python字典时,将能够编写出更可读、更紧凑的代码啦!Python版本你现在使用哪种版本的Python?3.7分?3.5分?还是2.7...

python 自学,字典3(一些例子)(python字典有哪些基本操作)

例子11;如何批量复制字典里的内容2;如何批量修改字典的内容3;如何批量修改字典里某些指定的内容...

Python3.9中的字典合并和更新,几乎影响了所有Python程序员

全文共2837字,预计学习时长9分钟Python3.9正在积极开发,并计划于今年10月发布。2月26日,开发团队发布了alpha4版本。该版本引入了新的合并(|)和更新(|=)运算符,这个新特性几乎...

Python3大字典:《Python3自学速查手册.pdf》限时下载中

最近有人会想了,2022了,想学Python晚不晚,学习python有前途吗?IT行业行业薪资高,发展前景好,是很多求职群里严重的香饽饽,而要进入这个高薪行业,也不是那么轻而易举的,拿信工专业的大学生...

python学习——字典(python字典基本操作)

字典Python的字典数据类型是基于hash散列算法实现的,采用键值对(key:value)的形式,根据key的值计算value的地址,具有非常快的查取和插入速度。但它是无序的,包含的元素个数不限,值...

324页清华教授撰写【Python 3 菜鸟查询手册】火了,小白入门字典

如何入门学习python...

Python3.9中的字典合并和更新,了解一下

全文共2837字,预计学习时长9分钟Python3.9正在积极开发,并计划于今年10月发布。2月26日,开发团队发布了alpha4版本。该版本引入了新的合并(|)和更新(|=)运算符,这个新特性几乎...

python3基础之字典(python中字典的基本操作)

字典和列表一样,也是python内置的一种数据结构。字典的结构如下图:列表用中括号[]把元素包起来,而字典是用大括号{}把元素包起来,只不过字典的每一个元素都包含键和值两部分。键和值是一一对应的...

取消回复欢迎 发表评论:

请填写验证码