有哪些函數式編程在前端的實踐經驗?
有哪些函數式編程在前端的實踐經驗?
微軟的 https://github.com/Reactive-Extensions/RxJS
用個FP語言編譯到JS寫界面也叫FP在前端的實踐, Promise是個Monad也叫FP在前端的實踐, 模仿個很多FP語言有的feature也叫FP在前端的實踐, FRP 雖然頭頂個Functional 但是原始FRP是有形式語義定義的, F不能單獨剝離出來討論…再說所謂的實踐藍到不是碰到什麼問題然後用工具去解決它木? 但是沒有見到這種答案, react能算上, 但是又沒人仔細說 BTW, 有人研究過這個么
- swannodette/mori · GitHub -- 從 clojurescript 移植到 js 的 immutable 數據結構,比 facebook 的 immutable.js 性能好
- jcouyang/conjs · GitHub -- 我 fork 的 mori 版本,集成 core.async aka CSP aka goroutine
- cognitect-labs/transducers-js · GitHub -- transducer 單獨的 javascdript port,當然 mori 已經包含了
- 入(rù-lang) -- 我的experiment,結合 macro 與 mori
- facebook/react · GitHub -- virtualdom 隔離開 mutable 的 dom,革命性的讓前端編程也 immutable
- http://ramdajs.com -- underscore 你錯了 https://www.youtube.com/watch?v=m3svKOdZijA
- cujojs/most · GitHub -- Monadic Reactive 編程
- http://folktalejs.org/ generic functional programming, monad 的實現不錯
謝邀。。。
其實函數式作為一種編程思想並沒有多麼複雜。這種思想同面向對象啊,MVC啊,設計模式啊一樣可以帶給我們解決問題的好辦法,舉一個最簡單的栗子——我們團隊維護著一個叫做NovaUI的移動組件庫——NovaUI-高性能的移動端組件庫某一天呢,我在項目中需要使用其中的 Swipable組件 , 但是呢,我發現一個問題,按照產品設計,我的swipable元素有一個偽滾動條,這個滾動條要根據Swipable組件的滾動條狀態同步更新,也就是說我必須拿到組件當前的滾動狀態,而這個狀態在組件設計對外的介面中並沒有暴露出來。那麼怎麼辦呢?無外乎有幾種辦法:
1) 放棄用這個組件來實現這個需求2) 自己修改組件,將滾動狀態暴露出來3) 讓團隊負責這個組件的同學升級組件4) 利用函數式編程思想,不修改組件的情況下對這個組件的方法做一些額外的修飾1)不說了,2)的問題是將來組件升級了我就不能享受新的功能,3)的問題是這個項目時間上不允許。因此我選擇了第四種方案——
var fn = {
watch: function(func, before, after){
return function(){
var args = [].slice.call(arguments);
before before.apply(this, args);
var ret = func.apply(this, arguments);
after after.apply(this, [ret].concat(args));
return ret;
}
}
}
var swipable = new Swipable({
element: ".swipable-wrap",
dir: "horizontal"
});
swipable._scroll = fn.watch(swipable._scroll, function(pos){
var p = pos / (swipable.min - swipable.max);
$("#progress-bar .current").css("width", Math.min(p, 1.0) * 240 + "px");
});
基本思路就是上面的代碼,首先定義一個watch的高階函數,它可以"watch"任意一個方法,在它被調用之前(before)和被調用之後(after)攔截進去。
然後我就watch了swipable組件中的_scroll方法,在它被調用前拿到了它的參數pos,進行計算,同步更新偽滾動條。
這樣的話我就在沒有對swipable組件本身改動一行代碼的情況下得到了我要的功能。
這種利用高階函數進行函數變換的思路,就是函數式編程的思路,這個例子就是用函數式編程思路解決問題的實際例子。它帶來的好處相信大家可以體會到的~
最後,給出完整栗子代碼在這裡—— swipable - w3ctech code
~~~~~~
更新:
我這裡再解釋一下上面的思路為啥說是functional的,我們知道,functional將函數本身看成輸入和輸出的映射,或者說是一種確定的運算。那麼我們可以定義兩個函數是否相等,對於任意函數a、b,如果給定的參數x、y,返回值z,當xa === xb且ya === yb時,總有za === zb,那麼我們認為a、b等價。當然,對於js的函數,除了這一點,還必須包括一個附加條件,那就是this上下文,也就是說,對於函數a、b當this上下文、參數都相同時,兩個函數總是返回相同的結果,那麼我們就說方法a和方法b完全等價。
可惜,以上這種解釋並不完全成立,這裡有一個「函數純度」的概念,對於常規的函數來說,每一次傳參確定,返回值是確定的,但可惜的是,這個規則並不完全成立,比如Math.random()、Date.now()這些函數每次調用的參數相同,返回值仍然不同,或者說,這些函數的「純度」比較低。那麼有這樣「低純度」的函數存在,我們就不能用上面的方法簡單來定義兩個函數「等價」
那麼如何定義任意兩個函數完全等價呢?我們可以有一個籠統的定義,也就是a、b等價意味著在系統中將a完全由b代替掉,系統運行的結果和原來相同。
看下面的範式——
function equal(func){
return function(){
var ret = func.apply(this, arguments);
return ret;
}
}
上面這個我把它叫做函數等價範式,這個範式是一個恆等範式。
也就是說,對於someObj.someMethod = equal(someObj.someMethod);
不論someObj和someMethod如何實現的,上面的賦值語句都完全不會讓代碼運行結果有一絲一毫改變。
有了這個等價範式,我們確保不會影響函數運算產生的結果,而不用考慮函數運算的具體過程,而且,我們可以在這個產生結果的b過程中添加我們的干預,只要我們的干預並不去影響a的輸入參數和返回值,那麼這層「干預」並不會對系統造成任何不可預知的影響。
function safeTransform(func){
return function(){
做一些事情,但不去改變 arguments
var ret = func.apply(this, arguments);
做一些事情,但不去改變 ret
return ret;
}
}
我們通過範式約定,用模式把對系統的影響控制在可控的範圍內,使得對系統的干預造成的風險可預知可控,而這一切,基於基礎的函數式編程原理。
大致上就這樣,實際上這個思路展開,還有很多好玩的可以討論的~現在最成熟的是clojurescript。它是clojure的一個子集,可以編譯成javascript。clojurescript編譯出來的javascript可以使用google closure compiler進一步優化,advanced optimization可以做dead code removal,非常強大。正常人很難寫符合google closure規範的javascript,但clojurescript編譯出來的javascript很容易符合google closure規範。加上clojure強大的各種庫,好用到哭的數據結構和清晰簡單的語法,使撰寫前端成為一種享受(先適應,再享受)。項目越大,狀態越複雜,就越享受。
此外,react的思想被業界接受後,clojurescript上的reagent, om等react的封裝庫,由於clojure本身的immutability,使得om/reagent寫出來的react UI比reactjs自身用javascript寫的效率好了非常多。elm對此做了個比較:gen/pages/blog/blazing-fast-html。
另外一個函數式語言elm,是haskell的子集,也是compile to javascript,其本身就支持FRP(functional reactive programming),目前最新版本是0.15,還不算穩定版。elm的想法來源於其創始人Evan的博士論文,其中一個重要思想是signal,具體可以看:https://www.youtube.com/watch?v=JreO-Kl0Ed4。目前這個項目由Prezi支持。Evan幾乎每月都會搞一次Elm的hackathon,我還沒參加過。
兩個語言各有千秋,都有repl便於開發,也都可以邊開發邊調試(代碼改動立即反應在browser,clojurescript需要figwheel庫支持),學習曲線很長,一旦上手習慣,生產力會大大高於javascript。
(本經驗只適用於 JavaScript)
嘗試過一段時間在使用 JavaScript 中運用 FP,有一些想法。不知道是缺乏某些認知的原因還是 FP 的固有屬性,感覺要想完全在 JavaScript 使用 FP 其實是很困難的事情。能使用一些 FP 的基本思想,不要過分追求 FP 就挺好的。
初學的時候會嘗試強迫自己去寫符合 FP 思想的代碼,例如強迫自己強調函數的「純」,沒有副作用,強調函數組合(composition),強迫自己能柯里化的地方就柯里化。後來會發現這樣寫很累,寫出來的代碼也不盡人意。舉一個前陣子我 FP 中毒的時候的例子:
async createPost () {
const isEmpty = _.anyPass([_.isEmpty, _.isNil])
const isPropEmptyMaybe = (prop) =&> _.ifElse(
_.pipe(_.prop(prop), isEmpty),
_.always(Either.Left(`${prop} 的值不能為空`)),
Either.Right
)
const isValidatePost = _.pipe(
isPropEmptyMaybe("title"),
_.chain(isPropEmptyMaybe("content")),
_.chain(isPropEmptyMaybe("tag")),
)
const alertProp = (prop) =&> _.pipe(_.prop(prop), alert)
const sendOrAlert = _.pipe(
isValidatePost,
_.ifElse(
_.pipe(Either.isLeft),
alertProp("value"),
_.map(this.sendPost)
)
)
sendOrAlert(this.state.post)
}
要是這個項目是個多人項目,估計同事抓狂然後把我五馬分屍。其實它實現的功能很簡單:就是把內容發到後台資料庫之前先做驗證,普通寫法 if else 就完事了。但是我為了追求 pointfree,追求沒有非函數的變數,組合了一堆的函數,還用了 Either 的 Monad,同事要搞懂要明白什麼是 Monad 還有 _.chain 是什麼鬼。
我還模仿過 Lisp/Scheme 裡面的 cons/cdr/car 構建了一個用純函數寫的分頁組件,把數據都放在了參數上,完全做做到了函數就是數據。我為了做到 Pure,嘗試給 HTTP 請求和 DOM、BOM 的操作加上了了 IO Monad,然後發現代碼全是 Monad。
這樣的例子很多,都是過了一陣子連我自己都看不懂的代碼。所以後來我終止了這種腦殘行為,不再過分追求 FP。就像輪子哥所說的那樣:
- 我們的大腦已經 hardcode 了非 Funtional 的神經網路,適應起來很困難。
- 大部分人都是從非 Funtional 的 C 語言系出來的。很多 Functional 的概念都沒有掌握,新手學習成本很高,實際項目你需要培養新人需要很大精力。
- 前端其實都是跟副作用打交道,HTTP 是副作用、DOM 是副作用,你 console.log 也是副作用(真是讓人絕望啊
- 寫出來的代碼可讀性真的很差
- ...
所以後來我就總結了,把一些 FP 的思想結合我們原來的神經網路就挺好的,它是錦上添花的作用,而不是雪中送炭。例如:
- 寫一些可讀性好的純函數,測試起來很方便
- Currying 和 Composition 有時候還是很好用的
- Monad 學一些,但是不要用起來,因為別人看不懂(捂臉
- ...
這些經驗僅限於 JavaScript,其他的 ClojureScript、Elm 的經驗可以參考大大的答案。
我接觸到的類庫(不完整):
Undescore/Lodash 模仿 Haskell Prelude 庫的各種數組操作
React 模仿多了... 不知道..Immutable-js 模仿 Clojure Haskell 當中的不可變數據Bacon.js 模塊 FRP 的流, 雖然模仿得不像Future/Promise 模仿 Haskell Monad 處理非同步的機制, Promise 在 Racket 里也有..ClojureScript 模仿 ClojurePureScript 模仿 HaskellLispyScript 模糊 Lisp 語法
Redux 模仿 ElmCoffeeScript 模仿 Lisp 系語言, 一切皆是表達式ES6 尾遞歸優化, 模仿 SchemeSweet.js 模仿 Lisp 系的 MacroFlow 語言, 模仿 Haskell 的類型系統... 雖然模仿別的更多函數式語言幾個特點(不嚴謹):
* 函數可以作為參數使用, 高階函數
* 純函數概念, 可以沒有返回值以外的內容* 不可變數據概念, 數據一經創建不可修改, 數據結構復用* 模式匹配
* 代數類型系統* Monad 對非同步, 對狀態的抽象* 惰性求值沾邊的應該還有很多...函數式語言有個很重要的概念:immutable,翻譯過來大概是不變數的意思。Facebook有個很火的庫,叫做 Immutable.js;immutable 的好處都有啥?誰說對了就給他(facebook immutable.js 意義何在,使用場景? - JavaScript )
不知道曾經在哪兒看到有推薦這本書,貼在這裡:《Mostly Adequate Guide to Functional Programming 》 主要講 JS 的。推薦我自己寫的電子書 《函數式編程樂趣(使用underscore.js)》,基本的map/reduce,集合操作,實例都有涉及。
UI = f(state)
ES6的arrow表達式借鑒了函數式編程里的lambda calculus。由於javascript語言層面一般是命令式、一半函數式,所以函數式的思想運用起來就特別多。比如: 函數是一等公民、閉包等等。另外coffeescript借鑒了lisp,livescript借鑒了haskell,如果有時間,用js擼個scheme解釋器或許你就清楚了。
明明是函數式編程語言,偏偏要模仿成面向對象語言來用!
rxjava
坐等張女神填坑
前端的邏輯就應該簡單,參考 UnderscoreJS。源碼可讀性非常高。
難道不是js
(話說js的創建者就是搞lisp的、的推薦閱讀:
※Vue.js 如何添加全局函數或變數?
※什麼樣的前端技術 leader 是稱職的?
※為什麼優酷在PC端瀏覽器依然用Flash?
※為什麼現在很多網站都不用原生的input輸入,而是用div模擬?
※前端構建工具是什麼?
TAG:前端開發 | JavaScript | 函數式編程 |