《JavaScript函数式编程思想》——列表

第8章  列表

函数式编程与列表处理有很深的渊源。列表是最基础,也是使用最普遍的复合数据类型。作为最早出现的函数式编程语言之一,Lisp【注:它的名称就来源于“列表处理器”(LISt Processor)】用函数参数和递归的方式来处理列表,既展示了列表的灵活性和表现力,又体现了函数式编程的优美和强大,影响了后续的很多编程语言。本章就来探讨JavaScript中的列表和函数式编程。

8.1  处理列表
8.1.1  函数的三种写法
8.1.2  处理列表的高阶函数
8.2  函数式编程的列表接口
8.2.1  没有副作用的方法
8.2.2  有副作用的方法

8.2.3  列表接口中的其他函数

列表是函数式编程最常使用的复合类型,除了数组方法对应的那些函数,函数式编程还发展出许多常用的列表函数,再配合高阶函数、柯里化和复合等技术,让列表的操作异常灵活,反过来又让列表的应用更加普及。这样的函数很多,下面介绍几个较常用的。

get和set:读写列表指定索引处的元素。列表是对象,读写列表元素只是读写对象属性的一种特例,所以get和set等其他读写对象的纯函数也是列表接口的成员。

drop:抛弃列表开头指定数目的元素,返回一个包含剩余元素的新列表。

export function drop(num, indexed) {
    return slice(num, indexed.length, indexed);
}

take:提取列表开头指定数目的元素,返回一个包含这些元素的新列表。

export function take(num, indexed) {
    return slice(0, num, indexed);
}

drop和take函数的功能简单,使用方便,slice函数可以看作是它们的组合。

flatten:将一个嵌套的数组“压扁”,即把内嵌数组的元素全部按顺序置于最外层的数组中。

export function flatten(arr) {
    return _flattern(arr, []);

    function _flattern(arr, flat) {
        for (let e of arr) {
            if (isArray(e)) {
                _flattern(e, flat);
            } else {
                push(e, flat);
            }
        }
        return flat;
    }
}

zip:将两个同样长度的列表“拉链”成一个,新列表中的每个元素是由给定的两个列表中对应的元素组成的一个长度为2的列表。

export function zip2(arr1, arr2) {
    return _zip(arr1, arr2, []);

    function _zip(arr1, arr2, accum) {
        if (arr1.length === 0) {
            return accum;
        }
        return _zip(rest(arr1), rest(arr2), push([first(arr1), first(arr2)], accum));
    }
}

 zip2函数可以推广到将任意多个同样长度的列表“拉链”成一个,新列表中的每个元素是由给定的n个列表中对应的元素组成的一个长度为n的列表。

export function zip(...arr) {
    //_zip函数声明和调用中的展开操作符都可以省略,那样的_zip函数就变成了unzip。
    return _zip([], ...arr);

    function _zip(accum, ...arr) {
        if (first(arr).length === 0) {
            return accum;
        }
        return _zip(push(map(first, arr), accum), ...map(rest, arr));
    }
}

反过来,我们可以写一个函数unzip,将一个被拉链的列表还原。unzip函数的参数是一个嵌套的列表,其中的每个元素都是一个长度相同的列表,返回的是与这些内嵌列表的长度相同数量的列表,每个列表由所有内嵌列表某个索引处的元素组成。聪明的读者在构思如何实现该函数时,会发现unzip的算法本质上与zip是相同的。把列表看成一个向量,多个向量组成一个矩阵。zip和unzip就是在矩阵的纵向向量和横向向量之间转换。因为矩阵的横向和纵向是对称的,所以两个函数本质上是相同的,可以相互定义。

export const unzip = apply(zip);

 zip是将多个列表中的元素组合成一个列表,这个“组合”的动作也可以推广到任意函数,这样返回的列表中的每个元素就是将该函数应用于多个列表的对应元素的结果。

export function zipWith(fn, ...arr) {
    return _unzip([], arr);

    function _unzip(accum, arr) {
        if (first(arr).length === 0) {
            return accum;
        }
        return _unzip(push(fn(...map(first, arr)), accum), map(rest, arr));
    }
}

zip可以看作是zipWith函数的一个特例,传递给zipWith的函数参数的功能就是将多个元素组成一个列表。

export const newArray = bind(Array, Array.of);

function zipWithArray(...arr) {
    return zipWith(newArray, ...arr);
}

 我们又发现zipWith与熟悉的map函数之间的关系,前者相当于将后者的单个列表参数扩展成任意多个列表,于是map也可以看作是zipWith函数的一个特例。

export const mapMany = zipWith;

8.3  小结

更多内容,请参看拙著:

《JavaScript函数式编程思想》(京东)

《JavaScript函数式编程思想》(当当)

《JavaScript函数式编程思想》(亚马逊)

《JavaScript函数式编程思想》(天猫)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章