简单介绍函数式编程中的Functor(函子),Applicative(加强版函子),Monad(单子)
原文地址:http://skaka.me/blog/2015/12/19/functor-applicative-monad-scala-haskell/
如果你是刚接触函数式编程,可能很容易被下面这些术语弄迷惑:Functor(函子),Applicative(加强版函子),Monad(单子)。 这些概念不是空穴来风,它们出自范畴论,如果你上网去搜范畴论,可能会找到大篇的术语定义,学术资料,这些资料大多都不是入门友好的。 这里我不会探讨定义,只会介绍这些概念在代码中到底起了什么样的作用,以及怎么样运用它们。
下面的示例代码大部分是Haskell,有一小部分是Java8,不会Haskell完全没关系,你可以把它们看作伪代码,我会对每一段代码进行解释。 这篇文章适合刚刚接触函数式编程的同学。我在刚接触这些概念的时候一头雾水,网上找的资料要么level太高看不懂, 要么直接就blabla给你介绍一大片背景知识了。后来经过长时间的摸爬滚打加实践,我发现这些概念理解起来也不是很困难,所以就想写一篇入门级的介绍。 如果你想要对函数式编程有一定的了解,这些概念你是绕不过去的,特别是Monad,当你发现你理解了Monad的机制,很多看起来不可思议的代码就能理解了。
开始之前,我简单介绍一下类型类(typeclass)和类型构造器的概念。函数式编程中的类型类是定义行为的接口。 如果一个类型是某个类型类的实例,那么这个类型必须实现所有该类型类所定义的行为。不要因为有“类”这个词就把类型类与面向对象中的类混淆, 他们是完全不同的概念。类型构造器能够接收其他类型为参数,创建出新的类型。举个例子,Scala的List即为接收一个类型参数的类型构造器, 当类型参数为Int时,List类型构造器的返回类型为List[Int],当类型参数为String时,返回类型为List[String]。 与类型构造器相对的概念是值构造器,比如Int(2)。
1. Functor
首先看看函子的类型类用代码怎么表示:
1 2 3 |
|
函子的类型类只定义了一个fmap函数: fmap函数接收两个参数,第一个参数是以a为参数,b为返回值的函数;第二个参数类型为f a,fmap的返回值类型为f b. 注意这里的a, b可以为任意类型, f为接收一个类型参数的类型构造器。这样说可能有点抽象,来看一个具体的例子。 已知[](列表)是一个functor实例,他的fmap函数声明为:
1 |
|
接收一个以a为参数,b为返回值的函数以及元素类型为a的列表,返回元素类型为b的列表。 至此,你能看出functor所抽象的行为吗?你可以从下面两个角度思考fmap: 1. 接受函数和函子值,返回在函子值上映射函数的结果(返回也是函子值)。 2. 接受函数,把该函数从操作普通类型的函数提升(lift)为操作函子值的函数。 这就是函子,不难吧?
2. Applicative
Applicative,俗称加强版函子,先来看Applicative类型类的代码:
1 2 3 |
|
多了一个新的元素,先解释下。 class (Functor f) => Applicative f
的意思是约束f类型必须首先是一个Functor(函子),即如果一个类型是Applicative的实例, 则肯定是Functor的实例。Applicative类型类定义了两个函数:pure和<*>
(其实还有一个fmap, 因为Applicative实例肯定是Functor的实例,所以fmap免费提供了)。pure是一个很简单的函数,接收任意类型的值为参数,返回包裹了该值的Applicative值。 <*>
函数看起来和fmap有些像,唯一的区别是fmap的第一个参数接收一个普通函数(a -> b),而<*>
的第一个参数为f(a -> b), 即把普通的函数用Applicative包裹。我们看看列表作为Applicative实例的实现:
1 2 3 |
|
pure的实现很简单,把接收的参数值放入列表并返回。<*>
的实现稍微复杂点,使用了列表生成式的语法。如果你接触过Python,对这种语法不会陌生, 这段代码如果用命令式语言风格翻译,会是这样:
1 2 3 4 5 6 |
|
到此为止,我们应该已经了解Applicative实例的作用了,主要定义了两个行为,第一个行为是接收一个任意值为参数,返回一个函子值, 第二个行为是从一个函子值里取出函数,应用到第二个函子里面的值。那么Applicative有什么实际用处呢,看下面的应用:
1 2 |
|
(*0)
是对*
函数的部分应用,*
是一个二元函数,接收两个参数,返回这两个参数的乘积。(*0)
是一个一元函数,接收一个参数, 返回这个参数与0的乘积。第一个列表里面是三个一元函数,分别应用到第二个列表的元素,参照我们之前对列表<*>
函数的定义,很容易得出上面的结果。 这种把<*>
串起来用的用法叫做Applicative风格,下面是另外几个例子:
1 2 3 4 |
|
非常好,现在我们能够把应用到普通值的函数应用到函子值上面了。
3. Monad
相比Functor和Applicative,Monad的应用更加广泛,Monad可以看作加强版的Applicative,引用<<Haskell趣学指南>>中的句子:
1 2 |
|
这种场景非常常见,我们来看Java8的Stream API:
1 2 3 4 5 |
|
flatMap方法接收一个函数(这个函数接收T类型的入参,返回Stream)然后在Stream上应用这个函数,返回Stream(暂时不考虑子类问题)。 我们来看看Haskell中的"flatMap"(Haskell中叫做绑定):
1 |
|
>>=
函数接收两个参数:一个Monad值m a,一个函数(入参为类型a,返回值为Monad值m b),返回值类型为Monad值m b。 这和Java8的flatMap形式上非常相似,其实它们要解决的是相同的问题。 Monad在函数式编程中无处不在,来看看具体的例子。
1 2 |
|
首先,列表是一个Monad,对照>>=
函数的定义,这里的m就是列表,a就是Int,a -> m b对应的类型就是Int -> [Int]。 上面的逻辑用Java8的flatMap实现:
1 2 |
|
好了,这里我简单介绍了Functor,Applicative,Monad的概念以及实际应用。如果对这些概念想更进一步了解,可以看看下面的书:
1. Haskell趣学指南
2. Functional Programming in Scala
3. 范畴论
简单介绍函数式编程中的Functor(函子),Applicative(加强版函子),Monad(单子)相关推荐
- 函数式编程中的战斗机(二) --运用elm语言MUV设计模式做一个简单的应用实例
@函数式编程中的战斗机(二) -运用elm语言MUV设计模式做一个简单的应用实例 1 elm语言设计模式的特点 1.1 面向对象设计模式的特点 每种编程语言都有其独特的语法和优缺点,从而导致与众不同的 ...
- 2. Python函数式编程中的字符串,元组,函数的分类,高阶函数,一篇文章都介绍一遍
函数式编程中的字符串 在函数式编程中,经常用到 Python 字符串,因其是不可变数据结构. 字符串本身是一个对象,具备很多对象方法,与常识中函数的使用不太相同,例如下述代码 my_str = &qu ...
- 函数式编程中的组合子
函数式编程是一个比较大的话题,里面的知识体系非常的丰富,在这里我并不想讲的特别的详细.为了应对实际中的应用,我们讲一下函数式编程中最为实用的应用方式--组合子.组合子本身是一种高阶函数,他的特点就是将 ...
- 函数式编程中的两个棘手问题
作者 | Matthew Butterick 译者 | 弯月 出品 | CSDN(ID:CSDNnews) 大约从十年前,我开始使用Lisp编程语言,后来我喜欢上了函数式编程.每当使用非Lisp语言 ...
- 函数式编程中的重要概念
函数式编程中的重要概念 函数式编程范式的意义 函数类型与高阶函数 部分函数 柯里化 闭包 递归 记忆化 原文地址 函数式编程范式的意义 在众多的编程范式中,大多数开发人员比较熟悉的是面向对象编程范式. ...
- Python函数式编程中map()、reduce()和filter()函数的用法
Python中map().reduce()和filter()三个函数均是应用于序列的内置函数,分别对序列进行遍历.递归计算以及过滤操作.这三个内置函数在实际使用过程中常常和"行内函数&quo ...
- 多角度让你彻底明白yield语法糖的用法和原理及在C#函数式编程中的作用
如果大家读过dapper源码,你会发现这内部有很多方法都用到了yield关键词,那yield到底是用来干嘛的,能不能拿掉,拿掉与不拿掉有多大的差别,首先上一段dapper中精简后的Query方法,先让 ...
- 如何在函数式编程中存在时间函数?
本文翻译自:How can a time function exist in functional programming? I've to admit that I don't know much ...
- 函数式编程中的副作用概念
前言 为了清楚起见,请记住,副作用不是必需的坏事,有时副作用是有用的(尤其是在函数式编程范式之外). 今天聊一聊函数式编程中的隔离思想,它所想隔离的就是"副作用" 我们先从其他角度 ...
最新文章
- SAP WM Storage Type Search配置里的Storage Class WPC标记
- htm、html、shtml网页区别
- Machine Learning - Andrew Ng on Coursera (Week 6)
- (Java多线程)线程状态
- Jsoncpp Compiler、Programming
- Visual Studio 2005 快捷键
- 再说千遍万遍,都不如这四句话管用,不服不行!
- python recv_[Python]关于socket.recv()的非阻塞用法
- TestStand系统框架简介
- LDAP认证-ldap介绍
- Spark大数据平台
- 【整理】system\app中的APK一览
- 嵌入式开发(一):嵌入式开发新手入门
- 吴裕雄--天生自然 诗经:醉翁亭记
- HCIP 综合实验(一)
- 前端 什么是响应式设计
- Java项目:SSM快递取件管理系统
- 基于RT-Thread+RA6M4的远程开机助手
- iOS App启动+广告思路
- 【菜鸡读论文】Former-DFER: Dynamic Facial Expression Recognition Transformer