點燈坊

失くすものさえない今が強くなるチャンスよ

使用 Function 重構 if else

Sam Xiao's Avatar 2020-08-27

if else 在每個程式語言都有,但這是 Imperative 使用方式,會使得 Function Pipeline 中斷,事實上可使用 Function 加以重構。

Version

macOS Catalina 10.15.6
Ramda 0.27.0
Wink-fp 1.20.85

if

let data = 'Sam'

let f = x => {
  if (x) return 'not empty'
}

f(data) // ?

很典型 if ,若 data 有值才印出 not empty

if000

&&

let data = 'Sam'

let f = x => x && 'not empty'

f(data) // ?

ECMAScript 的獨門武器,使用 && 可使 code 更精簡。

if001

when()

import { pipe, when, always } from 'ramda'
import { bool } from 'wink-fp'

let data = 'Sam'

let logNotEmpty = always('not empty')

let f = pipe(
  when(bool, logNotEmpty)
)

f(data) // ?
  • not empty 部分抽出獨立的 logNotEmpty()

  • 使用 when() 取代 if,但 when() 並沒有 falsy value 概念,因此需使用 bool() 明確將 String 轉成 Boolean

使用 when() 最大優點是可以整合在 Function Pipeline 內,這是 if 所辦不到的

if002

isNonEmpty()

import { pipe, when, always } from 'ramda'
import { isNonEmpty } from 'wink-fp'

let data = 'Sam'

let logNotEmpty = always('not empty')

let f = pipe(
  when(isNonEmpty, logNotEmpty)
)

f(data) // ?

由於 when() 第一個 argument 接受 function,因此對於 input 有加工的空間,除了使用 bool() 外,也能更明確使用 isNonEmpty() 使語意更為清楚。

if003

identity()

import { pipe, when, always, identity } from 'ramda'

let data = true

let logTrue = always('true')

let f = pipe(
  when(identity, logTrue)
)

f(data) // ?

data 已經是 Boolean,就不必靠 when() 的第一個 function 作轉換,此時可使用 identity() 維持不變。

iif004

iif()

import { pipe, always } from 'ramda'
import { iif } from 'wink-fp'

let data = 'Sam'

let logNotEmpty = always('not empty')

let f = pipe(
  iif(logNotEmpty)
)

f(data) // ?

也可使用 Wink-fp 的 iif(),它與 Ramda 的 when() 有以下差別:

  • iif() 底層為 &&,可接受 falsy value,而 when() 不行
  • iif() 不需要其他 function 轉換或 identity(),只接受 callback

若 data 需經過 function 處理則使用 when(),若可轉為 truthy value 或 falsy value 則使用 iif()

iif005

if not

let data = ''

let f = x => {
  if (!x) return 'empty'
}

f(data) // ?

很典型 if not ,若 data 為 empty string 有值才印出 empty

iif006

||

let data = ''

let f = x => x || 'empty'

f(data) // ?

ECMAScript 的獨門武器,使用 || 可使 code 更精簡。

iif007

unless()

import { pipe, unless, always } from 'ramda'
import { bool } from 'wink-fp'

let data = ''

let logEmpty = always('empty')

let f = pipe(
  unless(bool, logEmpty)
)

f(data) // ?
  • empty 部分抽出獨立的 logEmpty()
  • 使用 unless() 取代 if not,但 unless() 並沒有 falsy value 概念,因此需使用 bool() 明確將 String 轉成 Boolean

使用 unless() 最大優點是可以整合在 Function Pipeline 內,這是 if not 所辦不到的

iif008

isNonEmpty()

import { pipe, unless, always } from 'ramda'
import { isNonEmpty } from 'wink-fp'

let data = ''

let logEmpty = always('empty')

let f = pipe(
  unless(isNonEmpty, logEmpty)
)

f(data) // ?

由於 unless() 第一個 argument 接受 function,因此對於 input 有加工的空間,除了使用 bool() 外,也能更明確使用 isNonEmpty()

iif009

isEmpty()

import { pipe, when, isEmpty, always } from 'ramda'

let data = ''

let logEmpty = always('empty')

let f = pipe(
  when(isEmpty, logEmpty)
)

f(data) // ?

unless(isNotEmpty) 這種兩次反向邏輯很繞口,改用 when(isEmpty) 語意更好。

iif010

identity()

import { pipe, unless, identity, always } from 'ramda'
import { bool } from 'wink-fp'

let data = false

let logEmpty = always('empty')

let f = pipe(
  unless(identity, logEmpty)
)

f(data) // ?

data 已經是 Boolean,就不必靠 unless() 的第一個 function 作轉換,此時可使用 identity() 維持不變。

iif011

nif()

import { pipe, always } from 'ramda'
import { nif } from 'wink-fp'

let data = ''

let logEmpty = always('empty')

let f = pipe(
  nif(logEmpty)
)

f(data) // ?

也可使用 Wink-fp 的 nif(),他與 Ramda 的 unless() 有以下差別:

  • iif() 底層為 ||,可接受 falsy value,而 unless() 不行
  • nif() 不需要其他 function 轉換或 identity(),只接受 callback

若 data 需經過 function 處理則使用 unless(),若可轉為 truthy value 或 falsy value 則使用 nif()

iif012

if else

let data = 'Sam'

let f = x => {
  if (data) return 'non empty'
  else return 'empty'
}

f(data) // ?

很典型 if else ,若 data 有值回傳 not empty,否則回傳 empty

iif013

?:

let data = 'Sam'

let f = x => x ? 'non empty' : 'empty'

f(data) // ?

也可使用 ?: ternary operator 使用 code 更精簡。

iif014

ifElse()

import { pipe, ifElse, isEmpty, always } from 'ramda'
import { isNonEmpty } from 'wink-fp'

let data = 'Sam'

let logEmpty = always('empty')

let logNotEmpty = always('not empty')

let f = pipe(
  ifElse(isNonEmpty, logNotEmpty, logEmpty)
)

f(data) // ?
  • emptynot empty 部分抽出獨立的 logEmpty()logNotEmpty()
  • 使用 ifElse() 取代 if else,但 ifElse() 並沒有 falsy value 概念,因此需使用 isNonEmpty() 明確回傳 Boolean

使用 ifElse() 最大優點是可以整合在 Function Pipeline 內,這是 if not 所辦不到的

iif015

identity()

import { pipe, ifElse, identity, always } from 'ramda'

let data = true

let logTrue = always('true')

let logFalse = always('false')

let f = pipe(
  ifElse(identity, logTrue, logFalse)
)

f(data) // ?

data 已經是 Boolean,就不必靠 ifElse() 的第一個 function 作轉換,此時可使用 identity() 維持不變。

iif016

either()

import { pipe, either } from 'ramda'

let data = 'Sam'

let logEmpty = () => 'empty'

let logNotEmpty = () => 'not empty'

let f = pipe(
  either(logNotEmpty)(logEmpty)
)

f(data) // ?

也可使用 either() ,它與 ifElse() 有以下差別:

  • either() 可接受 falsy value,而 ifElse() 不行
  • either() 不需要其他 function 轉換或 identity(),只接受 function 即可

若 data 需經過 function 處理則使用 ifElse(),若可轉為 truthy value 或 falsy value 則使用 either()

iif017

Conclusion

  • 很多人覺得 FP 只能用於處理 Array,事實上邏輯也能用 FP 處理

  • if else 與 operator 其實都非 FP 產物,應盡量避免,而改用相對應 function 取代

Reference

Ramda, when()
Ramda, unless()
Ramda, ifElse()
Ramda, either()