參數 currying 與函數管道

在寫列表操作時,常會串接 map filter 最後 reduce, 簡化每個操作的內容。 在 javascript 中會用 dot notation 串接每個返回值的 map method, 但 scheme 中就沒辦法,要寫成巢狀不太方便, haskell 中就能搭配 pipe 和函數自動 currying 達成。

(filter (lambda (x) (= 1 (mod x 2)))
        (map (lambda (x) (+ x 3))
            (map (lambda (x) (* x 7))
                 '(sequence 10))))

在 scheme 中這樣寫很難閱讀, 反觀 javascript, 因為這些函數都是 Array 的方法, 就能用 dot notation 串接調用。

sequence(10)
  .map((x) => x * 7)
  .map((x) => x + 3)
  .filter((x) => x % 2 == 1)

我有想過建構一個 pipe 函數, 能把值依序傳給每個傳入的函數。

(define (pipe value . function-list)
  (fold-left (lambda (value function)
               (function value))
             value
             function-list))

(pipe 12 (lambda (x) (+ x 12))
         (lambda (x) (* x 2))
         (lambda (x) (mod x 7)))
;; 6

但用在陣列上就還要多寫一次 lambda。

(pipe '(1 2 3 4)
      (lambda (list)
        (map (lambda (x) (+ x 12))
             list))
      (lambda (list)
        (map (lambda (x) (* x 2))
             list))
      (lambda (list)
        (map (lambda (x) (mod x 7))
             list)))

而聽說 haskell 的函數,如果沒有給足參數數量, 就會變成半成品狀態,還是個函數, 再繼續傳入參數到足夠數量就能得到結果。 這是源自 curry 的用單參函數構造多參函數的方法。

// 假設 js 也有 currying
function add(a, b) {
  return a + b
}

const add3 = add(3)
add3(7) == 10
add3(6) == 9
add(2, 4) == 6

所以如果 scheme 也有參數 currying, 那上述的對列表的 pipe 直接傳入 (map (lambda (x) (* x 2))) 就同等於 (lambda (list) (map (lambda (x) (* x 2)) list))

// currying pipe list
map((x) => x+3, [1,2,3]) // [4,5,6]
const map4 = map((x) => x+4)
map4([1,2,3]) // [5,6,7]

pipe([1,2,3],
     map((x) => x * 3)
     map((x) => x + 6))
// [9, 12, 15]

更別提 haskell 還有 pipe 算符, 不用辛苦自己實現 pipe 函數。 傳入只差一個參數的函數,就能用作 pipe 單元。

雖然 scheme 可以用自幹出類似輔助函數的方式解決。

(define (apply-list list-operation atom-operation)
  (lambda ( . list)
    (apply list-operation 
           (cons atom-operation list))))
(pipe '(1 2 3 4)
      (apply-list map (lambda (x) (+ x 12)))
      (apply-list map (lambda (x) (* x 2)))
      (apply-list filter (lambda (x) (= 2 (mod x 7))))
      (apply-list fold-left (lambda (sum x) (+ sum x)) 0))

題外話,還是覺得 S 表達式好美, haskell 和一般的語言,包括其它偏指令式的, 用到一堆關鍵字 if else,= 賦值好醜。