2016年1月29日 星期五

淺談函數式編程和 React

函數是有定義介面的運算單元。

介面有用的地方是抽象化,你只需要知道輸入什麼(Input)、會得到什麼輸出(Output),你不用知道它細節是怎麼做到的。其實有個超能力在背後,也沒關係。當然這是在沒有碰到 bug 和有良好文件、不需要修改它的前提之下。

再強調一次,你只需要理解輸入(input)會對應到什麼輸出(output)就夠了。


問題來了,怎樣的輸入和輸出的對應「介面」會容易讓人理解?

1. 有意義的函數名稱 2. 多寫沒有副作用的純函數 1. 有可預測性: 每次輸入得到相同的輸出、函數內部不存狀態 2. 沒有副作用: 運算過程中不會改到外界的變數,像是不改傳進來的參數、不改可以存取的全域變數。 3. 顯式(Explict): 函數和外界溝通的管道只有,傳進來的參數和回傳值。 3. 簡化參數、對資料結構的 Information Architecture 做良好設計 4. 用函數來定義函數 1. 柯里化(Currying): 透過不同給參數來產生新的函數 2. 合成(Compose): 透過 pipeline 串接函數的input和output、隱藏參數,產生新的函數。

函數式編程為什麼強大、有彈性?

1. 把每個函數切得很小,容易更新、維護、平行處理、多人共同開發、被理解 2. 透過合成(Compose)把小函數變成大函數,比用繼承有彈性的多 3. 對集合實做 Functor 介面,讓一般函數都可以對集合操作。(array, matrix, tensor) 4. 把純和不純的函數分開來管理,容易找到問題點。

函數式編程為什麼難寫?

因為函數式編程想要把程式變簡單。但大家都知道「變複雜是簡單的、變簡單是複雜的」。所以這種方式寫程式需要設計、思考,你會是一個程式「設計師」。但如果你現在的專案寫完之後沒人會看、不會再改、不用維護、規模不大,也許函數式編程並不能幫到這個專案多少。

寫 React 就是實踐函數式編程

1. 透過自定義元件,定義畫 View 的抽象化函數樹 2. 透過 JSX 語法把小函數組合成大的函數 (等同於 compose) 3. 鼓勵大家寫 dumb 元件、也就是純函數 4. 用唯讀的 props,限制大家不能改傳進來的參數、減少副作用 5. 透過導入 flux,把純和不純的函數分開來管理 1. 純的 (對資料只做讀的動作):每個元件中的 render function 2. 不純的 (對資料做讀和寫的動作): flux中對 action 的 callback、redux 中的 reducer、父元件傳給子元件的 callback 3. glue code:元件中其他的 javascript

從函數式編程的角度看 React 的問題?

1. 沒有什麼高階的集合操作方法: 1. 把 lodash.js 集合操作拉進來用? 2. 用 lenses 的方式來穿透深長不露的 states? 2. 沒有簡單的 currying 語法 1. 像是傳不同參數給 html5 input 元件,生出各式各樣對數字的、email、submit、text的自定義元件。最簡單寫法應該是 2. export function NumberInput(props) { 角括弧input type=“number” {…props} /> } 3. PS: function 一定要有名字不然 debug 會很慘。 3. 雖然集中管理了不純的函數,但還是很難寫:現在透過修改 store 中的 state 來控制元件,這邊 action 觸發的都是不純的函數,如果元件架構一深,還是會很難改。有解嗎?這邊還沒找到什麼 coding guideline… 1. 多個 container smart 元件 2. flux 的多個 store 3. redux 的階層式 reducers ----------- 延伸閱讀: deku: functional alternative to react how to use classes and sleep at night (in a functional way)

2016年1月28日 星期四

Hey Underscore, You're doing it Wrong! (介紹函數編程)

----------------------------------------------------------
Curried Function:到拿到所有需要的參數前... 一直回傳新函數的函數。
var add = function(x) {
  return function(y) {
    return x + y;
  }
}

var add3 = add(3);
add3(4); //return 7

add(3)(4); //weird thing 
autoCurry in Wu.js will save us
var add = function(x, y) {
  return x + y;
}.autoCurry();

var add3 = add(3);
add3(4) //7

add(3,5) //8 => not weird any more!!!
但我們為什麼需要 curry?參考下面這個組成新 function getTheOdds 的例子。 有了currying,我們可以透過給予不同參數來建立新的函數。
var filter = function(f, xs) {
  return xs.filter(f);
}

filter(isOdd, [1,2,3,4,5]) // [1,3,5]

var getTheOdds = filter(isOdd);
getTheOdds([1,2,3,4,5]) //[1,3,5]
再來一個用loadash的酷例子
//沒用currying、不函數化的寫法
var firstTwoLetters = function(words){
  return _.map(words, function(word){
    return _.first(word, 2);
  });
}

//函數化的寫法(如果underscore吃參數的方式是反過來的話)
var firstTwoLetters = _.map(_.first(2));

//更函數化的寫法
_.map(_.first(2), ['jim', 'kate']) //['ji', 'ka'] 
=> Underscore.js的參數排列法讓currying變得不可能 總結currying的優點有下面四個: 1. 一般化函數、要傳的變數名消失了 2. 透過給不同參數就可以生成不同的函數 3. 更簡潔的定義 4. 讓函式的組合/合成 (composition) 變的可能 ---------------------------------------------------------- 組合/合成 (composition):用多個函數來組成新函數 簡單的例子,用 first() 和 reverse() 來合成 last 函數
var last = function(xs) {
  var sx = reverse(xs);
  return first(sx);
}

var last = compose(first, reverse);

last([1,2,3]) //3
另一個例子,chain backwardly
var wordCount = function(str){
  var words = split(' ', str);
  return length(words);
}

var wordCount = compose(length, split(' '));
wordCount("There is a way to save the world") //8
Category Theory: 多個函數組合(compose),作用域互相對應的理論。Connecting the dot. 總結組合: 1. 能從其他函數組成新函數 2. 組合過程中把參數藏起來 3. 極為高階的寫程式 4. 有數學理論在後面支持 ------------------------------------------------------------------ Functors map 打開了後面的 object 然後做一些事、再放回 object
var plus1 = function(x){ return x + 1 }

plus1([3]) //wrong!!

map(plus1, [3]) //4
剛剛舉的例子,map 只能操作 array object、但下面試圖用 map 操作所有 object
map(plus1, MyObject(3)) //MyObject(4)

MyObject = function(val) {
  this.val = val;
}

MyObject.prototype.map = function(f) {
  return MyObject(f(this.val));
}
如果對 object 定義了 map function,它就變成 functor null check的例子、Dynamic Safety:
map(plus1, Maybe(3)) //=> Maybe(4)

map(plus1, Maybe(null)) //=> Maybe(null)

Maybe = function(val) {
  this.val = val;
}

Maybe.prototype.map = function(f){
  return this.val ? Maybe(f(this.val)) : Maybe(null);
}
把 ES6 promise 變 functor 的例子
map(populateTable, $.ajax.get('/posts');

Promise.prototype.map = function(f) {
  var promise = new Promise();
  this.then(function(response){
    promise.resolve(f(response));
  });
  return promise;
}
再來一個和 html 合作的例子:對有和沒有 user_login 的情況下,更新歡迎頁面。
$div = $("#myDiv");

//dot 會把 user.name 拿出來
var getGreeting = compose(concat('Welcome '), dot('name'));

var updateGreetingHtml = compose($div.html, getGreeting);

map(updateGreetingHtml, Maybe(App.current_user));
underscore 不讓人 extend map 總結 functor 能: 1. 改變函數的行為卻不用變動 open/closed principle 2. 不光只有 map, 還有 reduce & compose 3. 直覺且非私人的 api 4. free formulas 5. 動態型別安全/檢查 ------------------------------------------------------------- 總結:underscore 能變得更加 functional。希望有更 functional 的 library