范畴论是用于函数组合的理论性概念。范畴论和函数组合它俩在一块儿就像发动机排量和马力,像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');
这对下一个主题函子很是有用。