顯示具有 composition 標籤的文章。 顯示所有文章
顯示具有 composition 標籤的文章。 顯示所有文章

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

2015年11月9日 星期一

函數式編程介紹 ( 1 / 2 )

函數式編程的專有名詞篇


純函數 ( Pure Function )給同樣輸入參數,就會回傳同樣結果的函數,而且沒有任何可觀察到的副作用。Pure Function 的例子:sin(x)。非 Pure Function 的例子:getChar()、random()、還有許多類別中的 member function。從數學上來理解,純函數就是一個數學函數,一個輸入會對到一個輸出。

純函數的特點:
  1. Portable / Self-Documenting :完全是自給自足的 ( self contained ),沒有外在的依賴 (Dependency)。
  2. 可暫存 ( Cacheable ):把計算值暫存的技巧被稱作 memorization,可以用來避免重複計算,加速程式。
  3. 好測試 ( Testable ):不需要管上下文和呼叫順序就可以測試。
  4. 適合平行處理、純函數是線程安全的 ( thread safe )。
  5. 引用透明 ( Referential Transparent ):一個引用透明的運算式指的是 如果這個運算式可以被他的值 (回傳值) 替換而不影響整個程式的行為。簡單講就是有可交換性啦。
  6. 可以熱抽換 ( Hot-Loading ):因為不依賴外部的狀態。

副作用 ( Side Effect )如果一個函數或運算式 ( expression ) 被說有副作用,這指的是它改變了一些狀態 ( states ) 或是 跟呼叫他的函數或外在世界,有可觀察到的互動。舉例來說,一個函數可能會改變全域變數或函數的靜態變數、改變傳進來的參數、引發例外 ( exception )、列印資料到螢幕或是呼叫了其他有副作用的函數。如果有了副作用,函數的行為會受到歷史、執行順序的影響。這樣子一來,想要理解或除錯有副作用的函式會較難,因為就必須要了解他的上下文 ( Context ) 和執行歷史。

顯式 ( Explicit ):形容函數与外界交換資料只有一個唯一管道——参數和回傳值。和顯式的相反是隱式 ( Implicit )。

一級函數( First-Class Function ):指的是語言支援把 Function 當成第一類公民,可以支援一般變數的操作,像是把 Function 當成變數傳給另外一個 Function。

Lambda Function:Lambda 運算式是匿名函式,可用來建立委派或運算式樹狀架構類型。

形式系統 ( Formal System ):形式系統可以的廣泛地被定義為任何基於數學模型的、良好的抽象思考的系統 ( system of abstract thought )。

Currying:這個技巧能把接受多參數的函數轉換為多個連續呼叫的單一參數函數。

閉鎖 ( Closure ) / MDN:Closure 是可以使用獨立 / 自由變數的函數。換句話說,Closure 記得它實體化時的環境變數。

PS:專有名詞那邊引用了很多別人的解釋,可以點前面的連結進去看完整版。

-----------------------------------------------------------------------------------------


函數式編程簡介篇

函數式編程的的哲學就是假設副作用 ( side effect ) 是不正確行為的主要原因。所以努力想要控制和管理副作用,經常的解法就是把純函數、單子和不純的函數 ( impure function ) 分開來管理。另外鼓勵大家多寫純函數。我們對待資料要像玩戲法般,一直傳來傳去、禁止使用狀態 ( state ) 和副作用。剛剛這段文字有提到很多專有名詞,但這樣子怎麼寫程式?這邊我開始介紹一個新工具叫 柯里化 ( currying )。

Currying:
Currying 把任何的函數轉換成 一連串只做單一事情的函數,各個擊破。

Curring的概念是簡單的。它讓你呼叫函數 A 時可以傳比預期還少的參數。然後這個函數 A 會回傳函數 B函數 B 需要的參數是先前沒傳進函數 A 的參數。所以整個流程是:

函數A (x, y) 可以等價於下面這段
-------------------------------------
函數B = 函數A (x)
函數B (y)

先前傳進去函數 A 的參數會利用閉鎖 ( Closure ) 的方式變成函數 B 的環境變數。看完上面這段解說,一定會想這什麼鬼東西?來看看 這邊的例子 會容易理解的多。利用這個技巧,就可以選擇一次傳所有參數或是把參數分幾次傳給數個函數。

lodash提供了把函數 curry化的工具,用法:

var divide =           function (x,y) { return x / y } 可以被改寫成
-----------------------------------------------------
var divide = curry( function (x,y) { return x / y } );

之後就可以被這樣呼叫

divide ( x ) ( y );

或是

var xDividedBy = divide ( x );
tenDividedBy ( y );

PS:這工具的名字是為了紀念一個美國數學家 --- Haskell Curry,跟咖哩沒有關係。

接下來我們看另外一個工具 代碼組成( compose )。

組合 / 組成 / 合成 ( compose )
Composition 把許多函數用管子 ( pipe ) 連接起來,變成一個新個函數。


f, g 都是函數,x 在它們之間被傳遞。
注意g函數的參數是 x、f函數的參數是 g函數的回傳值。
較常用在 f & g 都吃同一類參數的時候 (ex: 字串)。


例子:變大寫和去空白函數 = compose (變大寫函數, 去空白函數)

因為有了一級函數、Currying、組合,Pointfree 風格變得流行了起來,指的是函數呼叫時不用提到它要處理的資料。例如:

var snakeCase = function ( word ) {
      return word.toUpperCase( ).replace( /\s+/ig, '_' );


可被改寫成
-----------------------------------------------
var snakeCase = compose ( replace( /\s+/ig'_' ), toLowerCase );

用組合形成新函數就不用提到資料 --- 也就是之前寫法中的 word

Pointfree的編程風格可以讓我們移除不需要的名字 (names),讓我們保持簡潔 ( concise ) 和一般化 / 泛型 ( generic )。但要小心 Pointfree 是個雙面刃,有時會把原來的目的變得模糊。

( 以上是書本前五章的內容,其他待續... 後面有點硬,不知道什麼時候會看。)

---
這篇文介紹得很好 函數式編程