範疇論是用於函數組合的理論性概念。範疇論和函數組合它倆在一塊兒就像發動機排量和馬力,像NASA和空間穿梭, 像好酒和裝它的瓶子。基本上講,你不能讓它們中的一個脫離另外一個而獨立存在。javascript
範疇論實際並非一個很難的概念。在數學上它大到可以填滿一個本科課程,可是在計算機編程中它能夠很容易地被總結出來。html
愛因斯坦曾說過:「若是你不能把它解釋給一個六歲的孩子聽,那你本身也沒有理解」。這樣,按照給六歲孩子解釋的說法, 範疇論只不過是一些鏈接的圓點。也許這過度簡化了範疇論,不過這從直觀的方式上很好的解釋了咱們所須要知道的東西。java
首先你須要瞭解一些術語。範疇(category,也能夠說是種類)只是一些一樣類型的集合。 在JavaScript裏,它們是數組或對象,包含了明確指定爲數字、字符串、布爾、日期或節點等類型的變量。 態射(Morphism)是一些純函數,當給定一系列輸入時總會返回相同的輸出。 多態操做能夠操做多個範疇,而同態操做限制在一個單獨的範疇中。 例如,同態函數「乘」只能做用於數字,而多態函數「加」還能做用於字符串。編程
下圖展現了三個範疇——A、B、C,以及兩個態射——f和g數組
範疇論告訴咱們,當第一個態射的範疇是另外一個態射所需的輸入時,它們就能夠像下圖所示這樣組合:安全
f o g符號表明態射f和g的組合。如今咱們就能夠鏈接這些圓點。函數
真的是這樣,只是鏈接圓點。spa
咱們來鏈接一些圓點。範疇包含兩樣東西:prototype
這是數學賦予範疇論的術語,因此不幸與咱們的JavaScript的術語集有些衝突。 範疇論中的對象更像是表明一個指定數據類型的變量,而不是像JavaScript所定義的對象那樣具備一系列屬性和值。 態射只是使用這些類型的純函數。code
因此JavaScript應用範疇論很簡單。在JavaScript中使用範疇論意味着每一個範疇只使用一個特定的數據類型。 數據類型是指數字、字符串、數組、日期、對象、布爾等等。可是JavaScript沒有嚴格的類型系統,很容易出岔子。 因此咱們不得不實現咱們本身的方法來保證數據的正確
JavaScript中有四種原始類型:number、string、Boolean、function。咱們能夠建立類型安全函數, 返回變量或者拋出一個錯誤。這符合範疇論的對象定理。
var str = function(s) { if (typeof s === "string") { return s; } else { throw new TypeError("Error: String expected, " + typeof s + "given."); } } var num = function(n) { if (typeof n === "number") { return n; } else { throw new TypeError("Error: Number expected, " + typeof n + "given."); } } var bool = function(b) { if (typeof b === "boolean") { return b; } else { throw new TypeError("Error: Boolean expected, " + typeof b + "given."); } } var func = function(f) { if (typeof f === "function") { return f; } else { throw new TypeError("Error: Function expected, " + typeof f + " given."); } }
然而這裏重複代碼太多,而且不是很函數式。咱們能夠建立一個函數,它返回一個類型安全的函數。
var typeOf = function(type) { return function(x) { if (typeof x === type) { return x; } else { throw new TypeError("Error: " + type + " expected, " + typeof x + "given."); } } } var str = typeOf('string'), num = typeOf('number'), func = typeOf('function'), bool = typeOf('boolean');
typeof = (type) -> (x) -> if typeof x is type x else throw new TypeError("Error: expected, undefined given.") str = typeOf('string') num = typeOf('number') func = typeOf('function') bool = typeOf('boolean')
如今,咱們能夠利用這些函數讓咱們的函數像預期那樣運行。
// 未受保護的方法 var x = '24'; x + 1; // 會返回'241',而不是25 // 受保護的方法 // plusplus :: Int -> Int function plusplus(n) { return num(n) + 1; } plusplus(x); // 拋出錯誤,防止出現意外的結果
再來看個有點肉的例子。咱們想檢查Unix時間戳的長度,由JavaScript函數Date.parse()返回的值是數字而不是字符串, 咱們得用str()函數。
// timestampLength :: String -> Int function timestampLength(t) { return num(str(t).length); } timestampLength(Date.parse('12/31/1999')); // 拋出錯誤 timestampLength(Date.parse('12/31/1999').toString()); // 返回12
像這樣把明確地一個類型轉換爲另外一個類型(或者是相同的類型)的函數叫作態射。這符合範疇論的態射定理。 這裏強迫經過類型安全函數進行類型聲明,利用了這個機制的態射是咱們在JavaScript中展現範疇概念所需的一切。
另外還有一種重要的數據類型:對象。
var obj = typeOf('object'); obj(123); // 拋出錯誤 obj({x:'a'}); // 返回 {x:'a'}
然而,對象各不相同。它們能夠被繼承。任何非原始類型(number、string、Boolean、function)的東西都是對象, 包括數組、日期、元素等等。
沒有辦法知道一個對象是個什麼類型,也就是說無法經過typeof關鍵字知道JavaScript的對象的子類型是什麼, 因此咱們得想辦法。Object有個toString()函數,咱們能夠經過它變通實現這個目的。
var obj = function(o) { if (Object.prototype.toString.call(o) === "[object Object]") { return o; } else { throw new TypeError("Error: Object expected, something else given."); } }
一樣,對於各類對象,咱們要實現代碼重用:
var objectTypeOf = function(name) { return function(o) { if (Object.prototype.toString.call(o) === "[object " + name + "]") { return o; } else { throw new TypeError( "Error: '+name+' expected, something else given."); } } } var obj = objectTypeOf('Object'); var arr = objectTypeOf('Array'); var date = objectTypeOf('Date'); var div = objectTypeOf('HTMLDivElement');
這對下一個主題函子很是有用。