前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >什么是 Monad (Functional Programming)?函子到底是什么?ApplicativeMonad

什么是 Monad (Functional Programming)?函子到底是什么?ApplicativeMonad

作者头像
一个会写诗的程序员
发布于 2018-12-12 06:33:24
发布于 2018-12-12 06:33:24
4.7K00
代码可运行
举报
运行总次数:0
代码可运行

什么是 Monad (Functional Programming)?

道生一,一生二,二生三,三生万物。

  • 这里的“生”,就是 “apply 函数 ”(请注意,这里的 apply 动词)。
  • 道,就是 X,一、二、三、万物等就是 Y。当然,这里的“一、二、三、万物” 都是函数。
  • “从无开始编程” 。

函数式编程的精髓就在于,我们可以用好多好多小小函数,搭搭搭,组成一个个大函数,最终写出整个程序来。比如我们想写一个函数

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
h: A -> C

然后手头有两个子函数:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
f: A -> B
g: B -> C

于是我们用一个胶水函数

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
fun compose(f,g): A->C{
    return {x->g(f(x))}
}

把那两个小函数胶起来,做成我们要的 h 。

问题:f和g合并成了h,那么可以合并的函数需要符合什么条件呢?

Monad不就是个自函子范畴上的幺半群,这有什么难理解的(A monad is just a monoid in the category of endofunctors) —— Phillip Wadler

Monad工作原理包含两个部分:对原范畴组合成新的范畴,这个范畴对于Monad来说必须是幺半群Monoid,可以认为Monad是一系列自函子的组合,这种组合是一种转换,转换的结果是Monoid。

Monad有以下特征:

Monad是一种定义将函数(函子)组合起来的结构方式。

这些组合的方法都是符合结合律的。

而Monoid是元素对象的组合的范畴,如果这种元素对象是函数或函子(也可能是Pipe,这就复杂了去了 ),那么Monad是自函子的组合范畴,Monad也是一种特殊的Monoid子集。

有一个特殊幺元,能够和任何元素组合,导致的结果是不改变这些元素。

函子到底是什么?

一个函子Functor是任意类型,这些类型定义了如何应用 map (fmap in Haskell) 。 也就是说,如果我们要将普通函数应用到一个有盒子上下文包裹的值,那么我们首先需要定义一个叫Functor的数据类型,在这个数据类型中需要定义如何使用map或fmap来应用这个普通函数。这个函子Functor如下图:

image.png

fmap的输入参数是a->b函数,在我们这个案例中是(+3),然后定义一个函子Functor,这里是Haskell的Just 2,最后返回一个新的函子,在我们案例中,使用Haskell是Just 5。下图展示了函子内部工作原理(多了一层上下文的“盒子”封装):

image.png

第一步是将值从上下文盒子中解救出来,然后将外部指定的函数(+3)应用到这个值上,得到一个新的值(5),再将这个新值放入到上下文盒子中。是不是很形象生动?

Applicative

当我们的值被一个上下文包裹,就像函子Functor:

image.png

之前我们讨论的是如何将一个普通函数应用到这个函子中,现在如果这个普通函数也是一个被上下文包裹的:就叫 Applicative。它能知道如何应用一个被上下文包裹的函数到一个被上下文包裹的值中。

image.png

Monad

函子funtor是将一个普通函数应用到包裹的值:

image.png

Applicative应用一个包裹的函数到一个包裹的值:

image.png

Monad 则是将一个会返回包裹值的函数应用到一个被包裹的值上。

image.png

image.png

image.png

那么函子、applicative和Monad三个区别是什么?

image.png

  • functor: 应用一个函数到包裹的值,使用fmap/map.
  • applicative: 应用一个包裹的函数到包裹的值。
  • monad: 应用一个返回包裹值的函数到一个包裹的值。

面对对象(OOP)可以理解为是对数据的抽象,比如把一个人抽象成一个Object,关注的是数据。 函数式编程是一种过程抽象的思维,就是对当前的动作去进行抽象,关注的是动作。

image.png

名词+动词= 图灵机 + 函数式 =对象(状态) + process

自函子(Endofunctor)

什么是函数(Function)? 函数表达的映射关系在类型上体现在特定类型(proper type)之间的映射。

什么是自函数(Endofunction)?

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
identity :: Number -> Number

自函数就是把类型映射到自身类型。函数identity是一个自函数的特例,它接收什么参数就返回什么参数,所以入参和返回值不仅类型一致,而且值也相同。

接下来,回答什么是自函子(Endofunctor)之前,我们先弄清什么是函子(Functor)?

函子有别于函数,函数描述的是特定类型(proper type)之间的映射,而函子描述的是范畴(category)之间的映射。

那什么是范畴(category)?

我们把范畴看做一组类型及其关系态射(morphism)的集合。包括特定类型及其态射,比如Int、String、Int -> String;高阶类型及其态射,比如List[Int]、List[String]、List[Int] -> List[String]

接下来看看函子是如何映射两个范畴的,见下图:

image.png

图中范畴C1和范畴C2之间有映射关系,C1中Int映射到C2中的List[Int],C1中String映射到C2中的List[String]。除此之外,C1中的关系态射Int -> String也映射到C2中的关系List[Int] -> List[String]态射上。

换句话说,如果一个范畴内部的所有元素可以映射为另一个范畴的元素,且元素间的关系也可以映射为另一个范畴元素间关系,则认为这两个范畴之间存在映射。所谓函子就是表示两个范畴的映射。

澄清了函子的含义,那么如何在程序中表达它?

在Haskell中,函子是在其上可以map over的东西。稍微有一点函数式编程经验,一定会想到数组(Array)或者列表(List),确实如此。不过,在我们的例子中,List并不是一个具体的类型,而是一个类型构造子。举个例子,构造List[Int],也就是把Int提升到List[Int],记作Int -> List[Int]。这表达了一个范畴的元素可以映射为另一个范畴的元素。

List具有map方法,不妨看看map的定义:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
f :: A -> B
map :: f -> List[A] -> List[B]

具体到我们的例子当中,就有:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
f :: Int -> String
map :: f -> List[Int] -> List[String]

展开来看:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
map :: Int -> String -> List[Int] -> List[String]

map的定义清晰地告诉我们:Int -> String这种关系可以映射为List[Int] -> List[String]这种关系。这就表达了元素间的关系也可以映射为另一个范畴元素间关系。

所以类型构造器List[T]就是一个函子。

理解了函子的概念,接着继续探究什么是自函子。我们已经知道自函数就是把类型映射到自身类型,那么自函子就是把范畴映射到自身范畴。

自函子是如何映射范畴的,见下图:

image.png

图中表示的是一个将范畴映射到自身的自函子,而且还是一个特殊的Identity自函子。为什么这么说?从函子的定义出发,我们考察这个自函子,始终有List[Int] -> List[Int]以及List[Int] -> List[String] -> List[Int] -> List[String]这两种映射。 我们表述成:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
类型List[Int]映射到自己
态射f :: List[Int] -> List[String]映射到自己

我们记作:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
F(List[Int]) = List[Int]
F(f) = f
其中,F是Functor.

除了Identity的自函子,还有其它的自函子,见下图:

[图片上传失败...(image-db344c-1542218165324)]

图中的省略号代表这些范畴可以无限地延伸下去。我们在这个大范畴所做的所有映射操作都是同一范畴内的映射,自然这样的范畴就是一个自函子的范畴。

我们记作:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
List[Int] -> List[List[Int]]
List[Int] -> List[String] -> List[List[Int]] -> List[List[String]]
...

所以List[Int]、List[List[Int]]、...、List[List[List[...]]]及其之间的态射是一个自函子的范畴。


幺半群

[幺半群][1]是一个带有二元运算 : M × M → M 的集合 M ,其符合下列公理: 结合律:对任何在 M 内的a、b、c, (ab)c = a(bc) 。 单位元:存在一在 M 内的元素e,使得任一于 M 内的 a 都会符合 ae = e*a = a 。

接着我们看看在自函子的范畴上,怎么结合幺半群的定义得出Monad的。

假设我们有个cube函数,它的功能就是计算每个数的3次方,函数签名如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
cube :: Number -> Number

现在我们想在其返回值上添加一些调试信息,所以返回一个元组(Tuple),第二个元素代表调试信息。函数签名如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
f :: Number -> (Number,String)

入参和出参不一致,这会产生什么影响?我们看看幺半群的定义中规定的结合律。对于函数而言,结合律就是将函数以各种结合方式嵌套起来调用。我们将常用的compose函数看作此处的二元运算。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
var compose = function(f, g) {
  return function(x) {
    return f(g(x));
  };
};

compose(f, f)

从函数签名可以很容易看出,右边的f运算的结果是元组,而左侧的f却是接收一个Number类型的函数,它们是彼此不兼容的。

有什么好办法能消除这种不兼容性?结合前面所讲,cube是一个自函数Number -> Number,而元组(Number,String)在Hask范畴是一个自函子,理由如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
F Number = (Number,String) 
F Number -> Number = (Number,String) -> (Number,String)

假如输入和输出都是元组,结果会如何呢?

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
fn :: (Number,String) -> (Number,String)

compose(fn, fn)  

这样是可行的!在验证满足结合律之前,我们引入一个bind函数来辅助将f提升成fn.

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
f :: Number -> (Number,String) => fn :: (Number,String) -> (Number,String)
: 在Haskell中称为 liftM
var bind = function(f) {
  return function F(tuple) {
    var x  = tuple[0],
        s  = tuple[1],
        fx = f(x),
        y  = fx[0],
        t  = fx[1];

    return [y, s + t];
  };
};

我们来实现元组自函子范畴上的结合律:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
var cube = function(x) {
  return [x * x * x, 'cube was called.'];
};

var sine = function(x) {
  return [Math.sin(x), 'sine was called.'];
};

var f = compose(compose(bind(sine), bind(cube)), bind(cube));
f([3, ''])

var f1 = compose(bind(sine), compose(bind(cube), bind(cube)));
f1([3,''])
>>>
[0.956, 'cube was called.cube was called.sine was called.']
[0.956, 'cube was called.cube was called.sine was called.']

这里f和f1代表的调用顺序产生同样的结果,说明元组自函子范畴满足结合律。

那如何找到这样一个e,使得a*e=e*a=a,此处的*compose运算

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// unit :: Number -> (Number,String)
var unit = function(x) { return [x, ''] };

var f = compose(bind(sine), bind(cube));

compose(f, bind(unit)) = compose(bind(unit), f) = f

这里的bind(unit)就是e了。

到这里,思路逐步清晰了。在Haskell这类的强类型语言中,我们甚至可以组装自己的Tuple Monad。如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
type Tuple(Number,String)
flatmap :: Tuple -> Number -> Tuple -> Tuple
unit :: Number -> Tuple
map :: Tuple >>= unit
    :: Tuple -> Number -> Number -> Tuple

//compose
// flatmap 即 bind,中缀表达式一般是 >>=
Tuple >>= (Number -> Tuple) >>= (Number -> Tuple)

Monads for functional programming一书中介绍说monad是一个三元组(M, unit, *),对应此处就是(Tuple, unit, bind).

参考链接:

  1. Translation from Haskell to JavaScript of selected portions of the best introduction to monads I've ever read
  2. 我所理解的monad
  3. Monads for functional programming
  4. Functor, Applicative, Monad

函子functor是比函数更高阶的函数,函子是作用于两个范畴之间的函数,但是根本上也是一个函数,因此函子的类型与上面的函数类型差不多。假设两个范畴是 C和D, 其函函子是:

functor F: C -> D

函子functor原理   函数组合的方式有其特殊地方,这个特殊主要是由于我们组合的对象是函数,如果组合的对象是整数类型,两个整数组合成一个整数,这没有问题,但是你不能将两个函数类型组合起来还是和原来函数类型一样。比如我们将两个f函数f ∷ A → B组合起来,就不会得到还是A → B。

函子functor是比函数更高阶的函数,函子是作用于两个范畴之间的函数,可以简单认为是两个集合之间的映射。范畴的映射转换需要转换其中的元素和态射。

假设两个范畴是 C和D, 有一个函子functor F: C -> D ,这种写法类似函数写法,但是因为函子是范畴的函数,所以,其工作原理是进入范畴C和D内部,而范畴是由元素对象和态射箭头组成,因此函子就要分别作用于元素对象和态射箭头。

映射元素对象:C中的任何对象A转变成了D中的F(A);   映射态射箭头:C中的态射f: A -> B转变成了D中的F(f): F(A) -> F(B) 。   (组合箭头和元箭头映射这里省略)

函子这种映射实际是一种分解组合方式,对于这个过程我们可以用下面模拟形象地理解:

计算C集合中每个函数的"结果", 但是不组合它们. 将 F函数单独应用于C中每个函数的结果,我们就获得结果的集合的集合。 压平这两层集合,组合所有的结果。 (注意这里的组合方式将对应Monad的自然变换态射)。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2018.11.15 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
37·灵魂前端工程师养成-[项目]JS画皮卡丘
-多年互联网运维工作经验,曾负责过大规模集群架构自动化运维管理工作。 -擅长Web集群架构与自动化运维,曾负责国内某大型金融公司运维工作。 -devops项目经理兼DBA。 -开发过一套自动化运维平台(功能如下): 1)整合了各个公有云API,自主创建云主机。 2)ELK自动化收集日志功能。 3)Saltstack自动化运维统一配置管理工具。 4)Git、Jenkins自动化代码上线及自动化测试平台。 5)堡垒机,连接Linux、Windows平台及日志审计。 6)SQL执行及审批流程。 7)慢查询日志分析web界面。
DriverZeng
2022/11/08
4030
37·灵魂前端工程师养成-[项目]JS画皮卡丘
【CSS进阶】巧用伪元素before和after制作绚丽效果
原创:叫我詹躲躲 来源:掘金 链接:巧用伪元素before和after制作绚丽效果
微芒不朽
2022/09/06
1.8K0
【CSS进阶】巧用伪元素before和after制作绚丽效果
边框的巧妙应用
边框有一些特殊的属性,可以采用边框来实现对话框的效果,而且兼容性杠杠的,不过在ie6下面可能会遇到兼容性问题,在后面分析它。 首先,我们看看边框的组成结构: <div style="width
欲休
2018/03/15
1.1K0
边框的巧妙应用
38·灵魂前端工程师养成-[项目]让皮卡丘动起来
-多年互联网运维工作经验,曾负责过大规模集群架构自动化运维管理工作。 -擅长Web集群架构与自动化运维,曾负责国内某大型金融公司运维工作。 -devops项目经理兼DBA。 -开发过一套自动化运维平台(功能如下): 1)整合了各个公有云API,自主创建云主机。 2)ELK自动化收集日志功能。 3)Saltstack自动化运维统一配置管理工具。 4)Git、Jenkins自动化代码上线及自动化测试平台。 5)堡垒机,连接Linux、Windows平台及日志审计。 6)SQL执行及审批流程。 7)慢查询日志分析web界面。
DriverZeng
2022/09/26
3380
38·灵魂前端工程师养成-[项目]让皮卡丘动起来
你还在用图片做引导蒙层?
首先认识一下引导蒙层,如下图页面是一个蒙层,会在某个局部位置高亮我们需要重点突出的内容:
WecTeam
2019/12/16
2.8K0
你还在用图片做引导蒙层?
纯CSS3绘制的逼真,呆萌,超酷的CSS3动画纯CSS3人物行走动画 逼真炫酷CSS3动画纯CSS3绘制的小猫笑脸动画 超呆萌纯CSS3绘制可爱小男孩动画 超酷面部表情
纯CSS3人物行走动画 逼真炫酷CSS3动画 CSS3实在是太强大了,今天分享的CSS3动画非常神奇,它可以模拟人物行走,而且人物行走动画非常逼真。人物行走时的跨步动画时多张图片重叠实现的。有了这个
wblearn
2018/08/27
1.7K0
纯CSS3绘制的逼真,呆萌,超酷的CSS3动画纯CSS3人物行走动画 逼真炫酷CSS3动画纯CSS3绘制的小猫笑脸动画 超呆萌纯CSS3绘制可爱小男孩动画 超酷面部表情
给博客挂个春节灯笼
把下面的代码复制到后台,底部自定义内容即可 第一种灯笼样式 <!-- 灯笼样式开始 --> <style> @media only screen and (max-width: 760px) { .deng-box, .deng-box1 { display:none; } } @media only screen and (min-width: 768px) and (max-width: 1024px) {
R0A1NG
2022/02/19
1940
html小案例
html简单菜单栏<!DOCTYPE html><html lang="zh-CN"><head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <link rel="styleshe
JQ实验室
2022/10/16
6690
WordPress如何添加GO域名安全跳转教程
出于优化 SEO,或是出于加强网站安全又或许用户体验,很多博客都给文章中的外部链接加上了个二次跳转,本站也添加了GO跳转,因为这样可以给访问你网站的用户加强一下安全意识。
七辰
2023/10/05
8310
css笔记 - 张鑫旭css课程笔记之 border 篇
double可以利用来配合border-style:solid制作三条杠小icon
xing.org1^
2018/09/20
6040
css笔记 - 张鑫旭css课程笔记之 border 篇
CSS布局之需要掌握的小技巧
vertical-align属性对于inline元素、inline-block元素和table-cell元素有效,对块元素无效。
xinxin-l
2022/03/29
4270
CSS布局之需要掌握的小技巧
纯css实现117个Loading效果(中)
这是我这几十年间从世界各地寻觅到的 Loading特效,合计117个(本文贴出第40-78个),而且是 纯CSS 制作的。
德育处主任
2022/04/17
1.4K0
纯css实现117个Loading效果(中)
css(2)
font-family可以将多个字体名保存起来,如果浏览器不支持第一个字体会依次尝试后面的字体。
GH
2019/12/16
1.6K0
css(2)
CSS 3.0实现猫脸动画
给大家分享一个用CSS 3.0实现猫脸动画,实现的效果如下: 以下是实现的代码,欢迎大家复制粘贴和收藏。 <!DOCTYPE html> <html lang="en"> <head>
越陌度阡
2020/11/26
4370
CSS 3.0实现猫脸动画
8个硬核技巧带你迅速提升CSS技术
笔者选择了一些常用甚至有些小册都未提及到的干货作为分享内容,相信这些内容能帮助同学们在短期内提升CSS编码素质,实现一些看似只能由JS才能实现的效果。
javascript艺术
2021/05/28
2.9K0
8个硬核技巧带你迅速提升CSS技术
高质量编码-轨迹管理平台(CSS样式)
样式仿照百度地图鹰眼轨迹服务早期版本的web端轨迹管理台,地图和UI都使用了夜色风格。
MiaoGIS
2020/12/17
5350
高质量编码-轨迹管理平台(CSS样式)
忍术!猫眼三勾玉
如果你觉得我的代码还算有趣,在你的学习中能有所帮助,请查看我的置顶文章,我由衷感谢!  前端的学习不是一蹴而就,不积跬步无以至千里,不积小流无以成江海。持续不断的努力才能让你我有所收获。 效果图:
我不是费圆
2020/09/22
4960
忍术!猫眼三勾玉
CSS伪类:CSS3鼠标滑过按钮动画第二节
有了第一小节的经验,我们可以对直接的动画效果做一些升级效果,如果组合:before、:after,效果有更酷。
Javanx
2019/12/27
9010
CSS伪类:CSS3鼠标滑过按钮动画第二节
Ol4中晕圈点效果的实现
结合Openlayers4中的overlay,以图片作为晕圈背景,实现晕圈的点效果。
牛老师讲GIS
2018/10/23
7150
Ol4中晕圈点效果的实现
元旦网站挂灯笼,网站灯笼Css代码
最近又要到一年一度的元旦了,很多网站也开始挂上了灯笼,我也趁着这个机会水一篇文章,据我所知这段代码好久了,具体出处也无从考证,不过我看网上都是一边灯笼,我觉得不大好看就改了的两边灯笼的版本出来,代码会放在下面,按照自己的需要拿吧。
叮当叮
2020/12/29
2.3K0
相关推荐
37·灵魂前端工程师养成-[项目]JS画皮卡丘
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档