《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函數式編程思想》(天貓)

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