范畴论基本概念

如果你是第一次听说范畴论(category theory),看到这高大上的名字估计心里就会一咯噔,到底数学威力巨大,光是高等数学就能让很多人噩梦连连。和搞编程的一样,数学家喜欢将问题不断加以抽象从而将本质问题抽取出来加以论证解决,范畴论就是这样一门以抽象的方法来处理数学概念的学科,主要用于研究一些数学结构之间的关系及联系。

在范畴论里,一个范畴(category)指的是这样一个好东西,它由三部分组成:

  1. 一系列的对象(object).
  2. 一系列的态射(morphism).
  3. 一个组合(composition)操作符,用点(.)表示,用于将态射进行组合。

一个对象可以看成是一类东西,数学上的群,环,甚至简单的有理数,无理数等都可以归为一个对象,对应到编程语言里,可以理解为一个类型,比如说整型,布尔型,类型事实上可以看成是值的集合,例如整型就是由 0,1,2...等组成的,因此范畴论里的对象简单理解就可以看成是值(value)的集合。

一个态射指的是一种映射关系,简单理解,态射的作用就是把一个对象 A 里的值 va 映射为 另一个对象 B 里的值 vb,这和代数里的映射概念是很相近的,因此也有单射,满射等区分。态射的存在反映了对象内部的结构,这是范畴论用来研究对象的主要手法:对象内部的结构特性是通过与别的对象的关系反映出来的,动静是相对的,范畴论通过研究关系来达到探知对象的内部结构的目的。

组合操作符的作用是将两个态射进行组合,例如,假设存在态射 f: A -> B, g: B -> C, 则 g.f : A -> C.

看!好像没有想象中的复杂!一个结构要想成为一个范畴, 除了必须包含上述三样东西,它还要满足以下三个限制:

  1. 态射要满足结合律,即 f.(g.h) = (f.g).h。

  2. 态射在这个结构必须是封闭的,也就是,如果存在态射 f, g,则必然存在 h = f.g。

  3. 对结构中的每一个对象 A, 必须存在一个单位态射 Ia: A -> A, 对于单位态射,显然,对任意其它态射 f, f.I = f。

讲完了!范畴论就这么点东西!-- 当然是不可能的,但暂时来说,知道这些就已经很足够了。

Haskell 中的范畴

在 Haskell 中存在着这样一个唯一的范畴,名字称为 Hask, 这个 Hask 满足前面关于范畴的全部约定,因此是范畴论里一个纯正的“范畴":

  1. 对象就是 Haskell 里的所有类型,记得类型是一个集合。

  2. 态射就是编程语言里的一般函数(function),如: func :: Int -> Bool,将对象 int 映射为 对象 bool。

  3. 态射的组合就是函数的组合,在 Haskell 里,函数也是通过点号(.)进行组合的。

另外三个约束条件很容易证明也是满足,因此整个 Haskell 从数学的角度上看它就是一个范畴,这个角度的理解是很深刻的,这样一来传统意义上诸如语法,类型,函数等语言特性其实都只是这个内在本质的外在表现而已。

函子

前面对范畴的介绍反映了范畴内部各个对象之间的联系与相互作用,在范畴论里另外研究的重点是范畴与范畴之间的关系,就正如对象与对象之间有态射一样,范畴与范畴之间也存在某些映射,从而可以将一个范畴映射为另一个范畴,这种映射在范畴论中叫作函子(functor),具体来说,对于给定的两个范畴 A 和 B, 函子的作用有两个:

  1. 将范畴 A 中的对象映射到范畴 B 中的对象。

  2. 将范畴 A 中的态射映射到范畴 B 中的态射。

显然,函子反映了不同的范畴之间的内在联系,函子的定义是十分松散的,而不同范畴之间的关系有强有弱,一个随便定义的函子很多时候并不能太深刻反映范畴之间结构上的联系,因此数学上,对函子通常有几个限制,先假设 F 是范畴 A 与范畴 B 上一个函子,则:

  1. 对范畴 A 上的单位态射Ia, F 必须将其映射为范畴 B 上的单位态射 Ib, F(Ia) = Ib.

  2. 函子对态射的组合必须满足分配徤,即,假设 f, g 是范畴 A 上的态射,则 F(f.h) = F(f).F(g)。

显然这两个限制是很强的,如果两个范畴之间存在这样一个函子,则反映了他们之间在结构上有着很强的相似性,从看似风牛马不相及的东西里找出他们内在的相似性,数学家最爱干的事情了。

和态射一样函子也可以是自映射的,即函子允许将范畴映射到其自身,这样做有什么好处呢?不同范畴之间的映射反映了范畴间的相似性,范畴到范畴自身的映射则显然是反映了范畴内部的自相似性 --- 到底认识自己也不是一件容易的事啊。。。自相似性是大自然里美妙的存在,想想六角形的雪花,想想分形... 在范畴论里,这种将范畴映射到自身的函子被称为自函子(endofunctor).

Haskell 中的函子

知道为什么要讲自函子了吗,Haskell 中只有一个范畴! 那么这个唯一的范畴 Hask 中,存不存在自函子呢?有的!终于讲到重点了,为什么 Haskell 有这么些奇怪的概念? Haskell 的老鸟会告诉你,这些奇怪的东西都是宝贝,它们都是有本而来的。

那么 Haskell 中的自函子是怎么体现出来的呢? 根据前面的定义,一个函子其实就是一个映射,它把对象映射为对象,把态射映射为态射,我们知道在 Haskell 中对象就是一个类型,如整型,布尔型等,将一个类型映射为另一个类型,没错,就是 type constructor 在干的事情,c++ 的程序员可以用模板类来想象一下,如,vector<int> 其实就是将 int 映射为 vector<int>, 这是两种不同的类型了,实例化模板的过程实际上就是把一个类型变成另一个类型的过程。

注意不要把对象的映射与对象内部的态射混淆了,态射是将对象内部的值进行映射,而对象的映射(函子)是把对象这个整体映射为另一个对象,函子根本不关心一个对象内部会有什么值。

类型到类型的映射事实上并不是普遍存在的,自函子反映的是范畴内部的结构关系,这些关系并不是因为函子的存在而存在,函子只是揭示了这些内在的关系。具体在 Haskell 中,类型间的关系并不是普遍存在的,比如说, Int -> Bool 就没有直接对应的映射关系,而存在映射关系的类型,它们都有一些共同的特点,比如可以看成是简单类型与复杂类型之间的相互转换。

type constructor 就是自函子的一部分!

好了,现在类型到类型的映射在 Haskell 中找到了,那态射到态射之间的映射呢?必竟这也是函子的必要组成部分。

在 Haskell 中,态射就是一般的函数,把一个函数映射为另一个函数,听起来不就是高阶函数在干的事情嘛。具体来说,映射函数这件事可以认为来自 Functor 这个 typeclass,连名字都一模一样,目的昭然若揭。Haskell 中的 Functor 是一个 typeclass,它的定义如下:

fmap 干嘛的?显然就是用来把态射 (a -> b) 映射为态射 (f a -> f b)的,它把范畴里的态射映射到另一个态射,且遵守了函子在映射态射时所需要遵守的两个原则。

讲到这里,我们一步一步不知不觉就已经向着 monad 靠近了,好激动,先打住了,回头再整理整理。

【参考】

http://en.wikibooks.org/wiki/Haskell/Category_theory

http://bartoszmilewski.com/2011/01/09/monads-for-the-curious-programmer-part-1/

http://www.cnblogs.com/catch/p/3973104.html

Haskell 与范畴论-函子、态射、函数的更多相关文章

  1. Haskell 与范畴论

    说到 Haskell,这真是一门逼格极高的编程语言,一般初学者如果没有相关函数式编程的经验,入门直接接触那些稀奇古怪的概念,简直要跪下.现在回想起来,隐隐觉得初学者所拥有的命令式编程语言(impera ...

  2. Haskell 笔记(四)函数系统

    函数系统 函数式编程当然少不了函数系统啦,在教程最初的时候就有一个最简单的函数,函数系统贯穿在Haskell全部,Haskell的函数有几个重要的性质. 首先声明一下函数的参数和返回值类型 然后有一个 ...

  3. Haskell高阶函数

    Haskell functions can take functions as parameters and return functions as return values. A function ...

  4. haskell 常用 函数

    在学习haskell 记录以下常用的函数 随时更新! span  span :: (a -> Bool) -> [a] -> ([a], [a]) span, applied to ...

  5. Haskell学习-函数式编程初探

    原文地址:Haskell学习-函数式编程初探   为什么要学习函数式编程?为什么要学习Haskell?   .net到前端,C#和JavaScript对我来说如果谈不上精通,最起码也算是到了非常熟悉的 ...

  6. 《Haskell趣学指南》

    <Haskell趣学指南> 基本信息 原书名:Learn You a Haskell for Great Good!: A Beginner's Guide 原出版社: No Starch ...

  7. C++学习笔记35:函数模板

    函数模板 函数模板的目的 设计通用的函数,以适应广泛的数据型式 函数模板的定义格式 template<模板型式参数列表>返回值型式 函数名称(参数列表): 原型:template<c ...

  8. haskell Types 和 Typeclasses

    Algebraic Data Types 入门 在前面的章节中,我们谈了一些 Haskell 内置的类型和 Typeclass.而在本章中,我们将学习构造类型和 Typeclass 的方法. 我们已经 ...

  9. haskell模块(modules)

    装载模块 Haskell 中的模块是含有一组相关的函数,类型和类型类的组合.而 Haskell 进程的本质便是从主模块中引用其它模块并调用其中的函数来执行操作.这样可以把代码分成多块,只要一个模块足够 ...

随机推荐

  1. Python Excel操作库

    xlrd:支持.xls..xlsx读 xlwt:只支持.xls写 xlutils:只支持.xls读写 依赖于xlrd和xlwt xlwings:支持.xls读,.xlsx读写 可以实现Excel和Py ...

  2. node之Express框架

    Express是node的框架,通过Express我们快速搭建一个完整的网站,而不再只是前端了!所以Express还是非常值得学习的! express有各种中间件,我们可以在官方网站查询其用法. Ex ...

  3. Eclipse error: “The import XXX cannot be resolved”

    解决 Eclipse error: “The import XXX cannot be resolved” eclipse中修改: 1. 项目-->Properties-->java bu ...

  4. Jersey统一异常处理

    众所周知,java服务提供者提供给服务请求者应该是特定格式的数据,而不能出现异常栈类似信息,那么jersey中,如何添加统一的异常处理呢? 针对jersey启动如果是实现了ResourceConfig ...

  5. springboot整合rabbitmq,支持消息确认机制

    安装 推荐一篇博客https://blog.csdn.net/zhuzhezhuzhe1/article/details/80464291 项目结构 POM.XML <?xml version= ...

  6. C++程序设计基础(2)变量

    注:读<程序员面试笔记>笔记总结 1.知识点 (1)C++变量命名只能包含字母.数字.下划线,其中开头不能是数字:大小写敏感:习惯上变量用小写字母,常量.宏定义用大写字母. (2)变量的作 ...

  7. 解决npm install安装慢的问题

    国外镜像会很慢 可用 get命令查看registry npm congfig get registry 原版结果为 http://registry.npmjs.org 用set命令换成阿里的镜像就可以 ...

  8. .NET Core 部署到CentOS–1.创建项目,简单部署

    开发环境:Windows 10,部署环境:阿里云 CentOS 7.3 1. 创建应用 1) 创建项目, 配置应用生成部署包 2) 配置项目 编辑project.json, 追加环境项, 选项可参考这 ...

  9. 《C#高效编程》读书笔记02-用运行时常量(readonly)而不是编译期常量(const)

    C#有两种类型的常量:编译期常量和运行时常量.两者有截然不同的行为,使用不当的话,会造成性能问题,如果没法确定,则使用慢点,但能保证正确的运行时常量. 运行时常量使用readonly关键字声明,编译期 ...

  10. 将金额数字转换为大写汉字的js函数

    //将金额数字转换为大写汉字的函数 function convertCurrency(money) { //汉字的数字 var cnNums = new Array('零', '壹', '贰', '叁 ...