The Issue of the Day Before

跟著 Ramda 學 FP - 邏輯運算(3)

ramda -

不管是前面提到的 ifElse 或處理陣列的 takeWhile,都有一個判斷函式。

Ramda 提供一系列對邏輯運算操作的函式。整理如下,

value

func

&&

and

both

||

or

either

!

not

complement

^

xor

How

在函式風格的程式撰寫中,通常盡量使用表達式(Expression)而不是陳述式(Statement)來達成目的。

當然,沒必要強迫自己一定要全部使用表達式。

表達式最大的一個特徵就是它代表一個值。而這對 FP 特別有用。

函式的所有參數都必須是表達式,這也就是說每一個輸入參數都必須代表一個值。一個函式也是一個值。

因為 Ramda 提供邏輯運算的函式,所以我們能將邏輯運算的陳述式改為表達式來撰寫。

and

比較 andboth 簽名,

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

orand 是一樣的簽名。等效如下,

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。 當然也可以因為效能上的考量(其實差異不大)使用 botheither

這兩組的差異,除上述外,更重要的是 botheither 支援 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"]

andor 都是短路的。他們會傳回最後的結果。 透過 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)

這是因為函式也是一個 functornot 接受一個參數,透過 lift 就等效將函式的結果進行 not

xor

這在 Ramdajs 的對照中是比較特殊的,因為 Ramda 提供的 xor 函式,並無法進行位元運算。 由他的簽名可以很清楚的看到和 andor 不同。

and :: a -> b -> a | b
xor :: a -> b -> Boolean

xor 最後是傳回一個 Boolean 值,而不是 a | b

相對的 js 中並未提供做純邏輯判斷的 xor 算子,而 ^ 其實是一個位元算子,只是在某些情狀下會被用來當邏輯算子用。

閱讀在雲端