内容简介:Curry Function 最主要的目的在於 Function Composition,所以儘管是那 argument 該怎樣的設計才適合 composition 呢 ? 這就是本文的主題:Point-Free Style。F# 4.1
Curry Function 最主要的目的在於 Function Composition,所以儘管是 多
個 argument,最後也可變成多個 單一
argument 的 function 方便 composition。
那 argument 該怎樣的設計才適合 composition 呢 ? 這就是本文的主題:Point-Free Style。
Version
F# 4.1
ECMAScript 6
Ramda 0.25
FSharp
在學習 F# 時,由於 F# 是純 FP 語言,function 可自動成為 Curry Function,常發現 F# 的 function 會如此設計。
List.map()
mapping : (‘T -> ‘U) -> list : ‘T list -> ‘U list
List.filter()
predicate : (‘T -> bool) -> list : ‘T list -> ‘T list
List.reduce()
reduction : (‘T -> ‘T -> ‘T) -> list : ‘T list -> ‘T
僅管 List.map()
、 List.filter()
與 List.reduce()
三個 function 功能都不同,input 與 return 值也不同,但最後一個 argument 一定都是 list : 'T list
。
Q : 這樣設計 argument 到底有什麼優點呢 ?
Pipeline
let mapSquare = List.map (fun elm -> elm * elm) let filterOdd = List.filter (fun elm -> elm % 2 = 1) let sum = List.reduce (fun acc elm -> acc + elm) let calculate data = data |> mapSquare |> filterOdd |> sum calculate [1 .. 3] |> printf "%A"
第 1 行
let mapSquare = List.map (fun elm -> elm * elm) let filterOdd = List.filter (fun elm -> elm % 2 = 1) let sum = List.reduce (fun acc elm -> acc + elm)
List.map()
、 List.filter()
與 List.reduce()
都僅提供 1 個 argument,所以 mapSquare()
、 filterOdd()
與 sum()
都是 function。
第 5 行
let calculate data = data |> mapSquare |> filterOdd |> sum
將 mapSquare()
、 filterOdd()
與 sum()
透過 Pipeline 處理 data。
因為 mapSquare()
、 filterOdd()
與 sum()
的最後一個 argument 都是 list : 'T list
,在 Pipeline 時,F# 允許省略之。
|>為 F# 的 Pipeline, 由左至右
Function Composition
let mapSquare = List.map (fun elm -> elm * elm) let filterOdd = List.filter (fun elm -> elm % 2 = 1) let sum = List.reduce (fun acc elm -> acc + elm) let calculate = mapSquare >> filterOdd >> sum calculate [1 .. 3] |> printf "%A" // 10
第 1 行
let mapSquare = List.map (fun elm -> elm * elm) let filterOdd = List.filter (fun elm -> elm % 2 = 1) let sum = List.reduce (fun acc elm -> acc + elm)
List.map()
、 List.filter()
與 List.reduce()
都僅提供 1 個 argument,所以 mapSquare()
、 filterOdd()
與 sum()
都是 function。
第 5 行
let calculate = mapSquare >> filterOdd >> sum
將 mapSquare()
、 filterOdd()
與 filterOdd()
組合成 calculate()
。
因為 mapSquare()
、 filterOdd()
與 sum()
的最後一個 argument 都是 list : 'T list
,在 Composition 時,F# 允許省略之。
>>為 F# 的 Function Composition, 由左至右
我們可以發現 F# 在設計 function 時,會 故意
將要處理的 資料
放在最後一個 argument,將 條件
放在前面的 argument,如此所有 function 無論要做 Pipeline 或 Composition 時,都可省略最後一個 argument,讓程式碼更加簡潔。
Pipeline 與 Function Composition 講的其實是同一件事情,只是 F# 文化較喜歡使用 Pipeline,而 Haskell 較喜歡使用 Function Composition,稍後將統一使用 Function Composition
當初以為這只是 F# 的 syntax sugar,後來在歐陽繼超的 前端函數式攻城指南 與 Haskell 趣學指南 這兩本書,才發現這是 FP 特有風格,稱為 Point-Free Style 。
Definition
Point-Free Style
Function 不特別將要處理的 data 放進參 argument,因此也不回傳處理過的 data,而是回傳 function,這有助於 Function Composition,也稱為 Tacit Programming
Q:為甚麼 Point-Free Style 能成立呢 ?
let fn data = List.map (fun elm -> elm * elm) data
若原本 fn()
帶一個 argument,傳入要處理的 data
,相當於將 data
傳入 List.map (fun elm -> elm * elm)
,並回傳處理過的 data
。
let fn = List.map (fun elm -> elm * elm)
由於 F# 的 function 天生是 Curry Function, fn()
沒有 argument, =
左右將 data
同時消去,就相當於回傳 List.map (fun elm -> elm * elm)
function。
所以一個 function 將 data
放在最後一個 argument 時,提不提供 data
都成立:
-
有提供
data
則回傳處理過的data
-
不提供
data
則回傳 function
由於回傳是 function,特別適合做 Function Composition。
Q:為什麼稱為 Point-Free ?
Point
所指的就是傳入 data
的 argument, Point-Free
就是指 function 將 data
放在最後一個 argument,要使用時故意將最後一個 data
argumet 丟棄 (free),則變成回傳 function,但若 data
不是最後一個 argument,則無法丟棄,因此也無法變成 function,所以也無法繼續再做 Function Composition。
Q:為什麼 Point-Free Style 適合做 Function Composition ?
Function Composition 事實上來自於數學的 合成函數
,也就是 fog(x) = f(g(x))
,其中:
fog(x) = f(y) y = g(x)
也就是 g(x)
的 output 剛好為 f(y)
的 input,因此才能將 f(g(x))
合併,變成 fog(x)
,其中的 y
剛好被消滅。
若 f()
與 g()
每個 function 的 格式都一樣
,都是最後一個 argument 為 data
,則可將所有 function 加以組合成一個新 function,這就是 Function Composition。
JavaScript
前面談的都是 F#,你可能看得似懂非懂,我們就來將相同程式碼以大家熟悉的 JavaScript 改寫:
const _map = fn => data => data.map(fn); const _filter = fn => data => data.filter(fn); const _reduce = fn => data => data.reduce(fn); const mapSquare = _map(elm => elm * elm); const filterOdd = _filter(elm => elm % 2); const sum = _reduce((acc, elm) => acc + elm); const compose = (...fns) => fns.reduce((f, g) => (...args) => f(g(...args))); const calculate = compose(sum, filterOdd, mapSquare); const result = calculate([1, 2, 3]); console.log(result); // 10
第 1 行
const _map = fn => data => data.map(fn); const _filter = fn => data => data.filter(fn); const _reduce = fn => data => data.reduce(fn);
JavaScript 雖然都有提供 map()
、 filter()
與 reduce()
,但這些都是尚未 Curry 化的 function,無法使用 Function Composition,所以我們第一步就是將這些 function 改寫成 Curry Function。
第 5 行
const mapSquare = _map(elm => elm * elm); const filterOdd = _filter(elm => elm % 2); const sum = _reduce((acc, elm) => acc + elm);
再改寫成 Point-Free Style function。
第 9 行
const compose = (...fns) => fns.reduce((f, g) => (...args) => f(g(...args)));
由於 JavaScript 沒有提供 compose()
組合 function,我們自己土炮用 reduce()
寫一個 compose()
,負責將多個 Point-Free Style function 組合成單一 function。
12 行
const calculate = compose(sum, filterOdd, mapSquare);
將 mapSquare()
、 filterOdd()
與 sum()
組合成 calculate()
。
這裡與 F# 不一樣,而是 由右至左
。
JavaScript 雖然寫的出來,但由於沒有直接支援 Curry Function 與 compose()
,因此寫起來有點冗長
Ramda
import map from 'ramda/src/map'; import filter from 'ramda/src/filter'; import reduce from 'ramda/src/reduce'; import compose from 'ramda/src/compose'; const mapSquare = map(elm => elm * elm); const filterOdd = filter(elm => elm % 2); const sum = reduce((acc, elm) => acc + elm, 0); const calculate = compose(sum, filterOdd, mapSquare); const result = calculate([1, 2, 3]); console.log(result);
第 1 行
import map from 'ramda/src/map'; import filter from 'ramda/src/filter'; import reduce from 'ramda/src/reduce';
從 Ramda 引入 map()
、 filter()
、 reduce()
,這些都是已經是 Curry Function。
第 4 行
import compose from 'ramda/src/compose';
從 Ramda 引入 compose()
,這樣我們就不必自己實作 compose()
了。
第 6 行
const mapSquare = map(elm => elm * elm); const filterOdd = filter(elm => elm % 2); const sum = reduce((acc, elm) => acc + elm, 0);
將 Ramda 的 function 改寫成 Point-Free Style function。
10 行
const calculate = compose(sum, filterOdd, mapSquare);
使用 Ramda 的 compose()
將 mapSquare()
、 filterOdd()
與 sum()
組合成 calculate()
。
這裡與 F# 不一樣,而是 由右至左
。
Ramda 的版本就非常精簡,我們只需實作 Point-Free Style function 再加以組合即可,整體風格已經與純 FP 的 F# 非常接近
Summary
Q : Function Composition 該 由左至右
,還是該 由右至左
呢 ?
-
F# 的
>>
是由左至右
,優點是程式碼可讀性較佳 -
Haskell 的
.
與 Ramda 的compose()
是由右至左
,優點是與數學的fog(x) = f(g(x))
一樣由右至左
個人是偏好 F# 的 由左至右
,不過由於 Haskell 與 Ramda 的文化就是 由右至左
,也只能習慣了。
Conclusion
-
Point-Free Style 是 FP 設計 argument 的基本精神,這也是為什麼歐陽繼超在 前端函數式攻城指南
一書中指出 Underscore 設計錯誤,因為 Underscore 是
_.map([1, 2, 3], x => x + 1)
,將 data 放在第 1 個 argument,這並不符合 Point-Free Style - FP 首重觀念,只要心裡有 Function Composition,無論是在 F# 或在 JavaScript 都可實作
- 純 JavaScript 實作稍微冗長,若使用 Ramda,則整體精簡程度已經與 F# 非常接近
Reference
歐陽繼超, 前端函數式攻城指南
Miran Lipovaca, Haskell 趣學指南
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 【1】JavaScript 基础深入——数据类型深入理解与总结
- 深入理解 Java 函数式编程,第 5 部分: 深入解析 Monad
- 深入理解 HTTPS
- 深入理解 HTTPS
- 深入浅出Disruptor
- 深入了解 JSONP
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
图解CIO工作指南(第4版)
[日] 野村综合研究所系统咨询事业本部 / 周自恒 / 人民邮电出版社 / 2014-3 / 39.00
《图解CIO工作指南(第4版)》是一本实务手册,系统介绍了企业运用IT手段提高竞争力所必需的管理方法和实践经验,主要面向CEO或CIO等企业管理人士。 《图解CIO工作指南(第4版)》分为三个部分。第1部分的主题为IT管理,着重阐述运用IT技术提高企业竞争力所必需的所有管理业务,具体包括制定作为企业方针的IT战略,以及统筹执行该战略时与IT相关的人力、物力、财力、风险等要素在内的一系列管理业......一起来看看 《图解CIO工作指南(第4版)》 这本书的介绍吧!