JS函數式編程【譯】5.1 範疇論

範疇論

範疇論是用於函數組合的理論性概念。範疇論和函數組合它倆在一塊兒就像發動機排量和馬力,像NASA和空間穿梭, 像好酒和裝它的瓶子。基本上講,你不能讓它們中的一個脫離另外一個而獨立存在。javascript

範疇論概覽

範疇論實際並非一個很難的概念。在數學上它大到可以填滿一個本科課程,可是在計算機編程中它能夠很容易地被總結出來。html

愛因斯坦曾說過:「若是你不能把它解釋給一個六歲的孩子聽,那你本身也沒有理解」。這樣,按照給六歲孩子解釋的說法, 範疇論只不過是一些鏈接的圓點。也許這過度簡化了範疇論,不過這從直觀的方式上很好的解釋了咱們所須要知道的東西。java

首先你須要瞭解一些術語。範疇(category,也能夠說是種類)只是一些一樣類型的集合。 在JavaScript裏,它們是數組或對象,包含了明確指定爲數字、字符串、布爾、日期或節點等類型的變量。 態射(Morphism)是一些純函數,當給定一系列輸入時總會返回相同的輸出。 多態操做能夠操做多個範疇,而同態操做限制在一個單獨的範疇中。 例如,同態函數「乘」只能做用於數字,而多態函數「加」還能做用於字符串。編程

下圖展現了三個範疇——A、B、C,以及兩個態射——fg數組

範疇論告訴咱們,當第一個態射的範疇是另外一個態射所需的輸入時,它們就能夠像下圖所示這樣組合:安全

f o g符號表明態射f和g的組合。如今咱們就能夠鏈接這些圓點。函數

真的是這樣,只是鏈接圓點。spa

我插兩句:做者老說範疇論就是圓點,可是也沒看到個圓點。能夠認爲 f o g中間的圓圈就是圓點 (中間不是字母o而是圓圈,我實在找不到合適的字符)。由於在haskell裏,函數組合的形式就是f.g。

類型安全

咱們來鏈接一些圓點。範疇包含兩樣東西:prototype

  1. 對象 Object(在JavaScript中是類型)。
  2. 態射 Morphisms(在JavaScript中是隻做用於類型的純函數)。

這是數學賦予範疇論的術語,因此不幸與咱們的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');

這對下一個主題函子很是有用。

相關文章
相關標籤/搜索