跟著 Ramda 學 FP - 邏輯運算(3)
ramda and or both either not complement xor -不管是前面提到的 ifElse 或處理陣列的 takeWhile,都有一個判斷函式。
Ramda 提供一系列對邏輯運算操作的函式。整理如下,
value |
func |
|
&& |
and |
both |
|| |
or |
either |
! |
not |
complement |
^ |
xor |
How
在函式風格的程式撰寫中,通常盡量使用表達式(Expression)而不是陳述式(Statement)來達成目的。
當然,沒必要強迫自己一定要全部使用表達式。
表達式最大的一個特徵就是它代表一個值。而這對 FP 特別有用。
函式的所有參數都必須是表達式,這也就是說每一個輸入參數都必須代表一個值。一個函式也是一個值。
因為 Ramda 提供邏輯運算的函式,所以我們能將邏輯運算的陳述式改為表達式來撰寫。
and
比較 and 和 both 簽名,
and :: a -> b -> a | b both :: (*… -> Boolean) -> (*… -> Boolean) -> (*… -> Boolean)
從上可以看出 and 直接返回結果,但 both 返回的是函式。
and 其實就是 && 的封裝。可以像下面這樣寫,
const and = curryN(2, (a, b) => a && b)
簡單比較兩種寫法,如下
const a = true
const b = true
// Statement
if (a && b) {
result = true
} else {
result false
}
// Expression
result = and(a, b)
可以看出表達式地寫法因為直接傳回值的關係,該值可以直接提供給下一個函式使用。 而陳述式地寫法則必須有一個可變的變數。
而優點不只是得到一個乾淨的不可變的變數。
Ramda 提供的函式大部分都是支援 curry 的,所以可以像下面這樣來使用,
const father = true const parents = and(father) // ... parents(mother)
不需要同時給兩個參數,而是先使用一個參數將他變成一個函式,等待之後需要時在使用。
or
or 跟 and 是一樣的簽名。等效如下,
const or = curryN(2, (a, b) => a || b)
both
同樣的,and 判斷的是兩個值, both 卻是要求要吻合兩個判斷式。
從 both 延伸有一個更通用的函式 allPass,比較兩者的簽名如下,
both :: (*… -> Boolean) -> (*… -> Boolean) -> (*… -> Boolean) allPass:: [(*… -> Boolean)] → (*… -> Boolean)
兩者都傳回函式 (*… -> Boolean),both 要求兩個判斷式都為 true 才傳回 true,
而 allPass 接受一串判斷式的陣列,必須陣列內的所有判斷式都為 true 才得到 true。
either
either 的簽名與 both 相同。要求是任一判斷式為 true 就傳回 true。
同時也有更通用的函式 anyPass,只要陣列內的任一判斷式為 true 就傳回 true。
所以你可以基於日後修改的方便性使用 allPass 替代 both 或用 anyPass 替代 either。
當然也可以因為效能上的考量(其實差異不大)使用 both 或 either。
這兩組的差異,除上述外,更重要的是 both 或 either 支援 functor。
這代表你可以使用他們來比較兩個陣列,因為陣列也是一個 functor。
both([false, false, 'a'], [11])
//=> [false, false, 11]
either([false, false, 'a'], [11])
// => [11, 11, "a"]
上面的例子,等效於
const both = lift(and)
// lift(and)([false, false, 'a'], [11])
// => [and(false, 11), and(false, 11), and('a', 11)]
// => [false, false, 11]
const either = lift(or)
// lift(or)([false, false, 'a'], [11])
// => [or(false, 11), or(false, 11), or('a', 11)]
// => [11, 11, "a"]
and 和 or 都是短路的。他們會傳回最後的結果。
透過 lift 最後能得到一個二維的結果。示意如下,
both:
both |
false |
false |
'a' |
11 |
false |
false |
11 |
either:
either |
false |
false |
'a' |
11 |
11 |
11 |
'a' |
如果你給的是一個 3 元素陣列跟 2 元素陣列,最後得到就是一個 6 元素陣列。
not, complement
not 等效方法如下,
// not :: * -> Boolean
const not = (a) => !a
比較出人意外的 complement 的等效方法是這樣寫的,
const complement = lift(not)
這是因為函式也是一個 functor。
not 接受一個參數,透過 lift 就等效將函式的結果進行 not。
xor
這在 Ramda 與 js 的對照中是比較特殊的,因為 Ramda 提供的 xor 函式,並無法進行位元運算。
由他的簽名可以很清楚的看到和 and 或 or 不同。
and :: a -> b -> a | b xor :: a -> b -> Boolean
xor 最後是傳回一個 Boolean 值,而不是 a | b。
相對的 js 中並未提供做純邏輯判斷的 xor 算子,而 ^ 其實是一個位元算子,只是在某些情狀下會被用來當邏輯算子用。