Function 與 Array 排列組和執行

實務上常遇到一個 Array 中的所有 Function,必須對另外一個 Array 中所有 Data 都執行過一次,此時 Ramda 的 ap() 就是我們的好幫手。

Version


VS Code 1.31.1
Quokka 1.0.136
Ramda 0.26.1

Imperative


1
2
3
4
5
6
7
8
9
10
11
12
const hasAllTags = (rules, tags) => {
let result = true;

for (let x of rules) {
result = result && tags.some(y => y === x);
}

return result;
};

console.log(hasAllTags(['a', 'd'], ['a', 'e', 'd']));
console.log(hasAllTags(['a', 'd'], ['a', 'e']));

條件為 array 的元素必須包含 ad,若符合條件則回傳 true,否則回傳 false

若使用 imperative 寫法,會先定義 rules array,然後定義 result,預設為 true

由於 rules 是 array,因此必須使用 for loop 一一檢查傳進來的 array 是否包含了 rules 的每個元素,因此使用了 Array.prototypesome() 判斷。

最後將每個 result 判斷 && 起來,回傳結果,這是典型 Imperative 作法。

ap002

ap()


1
2
3
4
5
6
7
8
9
10
import { compose, all, ap, map, includes, of, identity } from 'ramda';

const hasAllTags = rules => compose(
all(identity),
ap(map(includes, rules)),
of
);

console.log(hasAllTags(['a', 'd'])(['a', 'e', 'd']));
console.log(hasAllTags(['a', 'd'])(['a', 'e']));

其實由 imperative 作法,已經可以看出一些端倪:

  • && 可用 Ramda 的 all() 取代
  • some() 可用 Ramda 的 includes() 取代

for loop 該用什麼 Ramda operator 取代呢 ?

Imperative 作法中,首先必須對 rules array 加以展開,執行每個 includes(),也就是說:rules array 有幾筆,其實 includes() 就要執行幾次。

1
map(includes, rules)

因此可用 map()rules 加以展開,成為:

1
2
3
4
[
includes('a'),
includes('d'),
]

傳統觀念 map() 都是用來產生 data,但由於 Ramda operator 都有 currying 特性,也此也可以使用 map() 配合其他 operator 來達成 function array,真正落實 FP 之 function as data 理念。

1
ap(map(includes, rules)),

且由於每個 includes() function 都要對傳入的 tags 做測試,這剛好符合 Ramda ap() 格式。

map() 產生的 function array,將對所有 data 都執行一次。

ap()
[a → b] → [a] → [b]

Function array 的所有 function 對 data array 的所有元素都執行一次,並將 function 傳回新 array

[a -> b]:以 function 所構成的的 array

[a]:要執行 function 的 data,所有元素都會執行 function 一次

[b]:function 執行的結果,以 array 構成,其個數為 function 個數 * 元素個數

1
of

ap() 要的是 人造 的 array of data,因次必須再使用 Ramda 的 of() 先做加工。

of()
a -> [a]
將 data 包裝成 array

1
all(identity),

最後再使用 all()ap() 的結果做判斷,取代原本的 &&,由於 all() 要求的是 function 而非 value,因此要加上 identity(),將 data 轉成 function。

all()
{a -> Boolean} -> [a] -> Boolean
若所有的 predicate 都成立則回傳 ture,否則回傳 false

ap001

Conclusion


  • 若要使用 for loop 將一堆 function 對 array data 執行時,可先使用 map() 將一堆 function 做成 function array,然後再使用 ap() 使每個 function 對 data 都執行

  • ap() 在實務上常要搭配 of(),產生人造的 array 才能對 function array 加以排列組合執行

Reference


Buzz De Cafe, The Tao of λ: Applicatives, Ramda style
Ramda, ap
Ramda, of
Ramda, compose
Ramda, all
Ramda, map
Ramda, includes
Ramda, identity