这是我为您可能从未在其他任何地方找到的 monads 初学者做出贡献的尝试。
monad 是函数式编程中高度可组合的单元(一种编程的构建块)。
(IMO,在没有任何上下文和合理化的情况下引入“Monad 定律”只是一种无用的分类和理解这个概念的危险。不用担心,我会在本文后面完成这项工作。)
在大多数情况下,我们有多种编程构建块,例如对象、函数、列表等。
尽管拥有多种编程块似乎是自然规律,并且对于实际用途的灵活编程来说是不可避免的,但事实上,拥有多种块是编程环境污染的主要来源之一。
使用各种块构建块是一项复杂的任务。程序员需要在各种情况下从各种块中非常明智地选择一个块,并且在很长一段时间内,他会失败。
因此,不鼓励根据情况选择各种块,相反,始终使用某个普遍标准化的预选块是一个很好的原则。
事实上,这种智慧在当今的 PC 世界中很常见。
USB 是通用串行总线的缩写,是一种行业标准,旨在定义用于个人计算机及其外围设备之间的连接、通信和电源的电缆、连接器和协议。
获得精心设计的通用标准化构建块可以消除许多问题。
- 对象是(曾经是)那个。
- 功能是唯一的。
- 单子就是一个。
- 规格
- 执行
- 确认
1.OOP
面向对象编程(OOP)是一种基于“对象”概念的编程范式,它可能包含数据,以字段的形式,通常称为属性;和代码,以过程的形式,通常称为方法。对象的一个特征是对象的过程可以访问并经常修改与其相关联的对象的数据字段(对象具有“this”或“self”的概念)。在 OOP 中,计算机程序是通过将它们制作成相互交互的对象来设计的。OOP 语言有很大的多样性,但最流行的语言是基于类的,这意味着对象是类的实例,这通常也决定了它们的类型。
选择对象作为通用标准化的构建块,程序员准备一个包含成员值和函数的基类,并且为了获得块的变体,使用继承。
面向对象的思想通常是用现实世界的物理对象来解释的,而范式本身在数学抽象方面很薄弱。
例如,函数(或方法)从属于对象,函数不需要是一等对象,这是理所当然的,因为范式最初选择对象作为其精心设计的通用标准化构建块。
功能是作为标准化构建块的对象的从属实体并且两者的角色完全不同的观点来自物理世界中的工程意义。不是编程实际所在的数学抽象。
OOP 的基本问题只是对象不是精心设计的通用标准化构建块。具有强大数学背景的函数式编程或 monad 是更好的选择。
2.函数式编程
函数式编程就是组合函数。
说起来容易,但这已经是编程史上的一大成就了。
与其研究编程的悠久历史,不如分享我的个人历史。
我从1.0版本开始就是C#(OOP)程序员,总体上还算满意,但感觉很不对劲,但不知道是什么问题。
后来我成为了一名 JavaScript 程序员,在早期,我曾经这样写:
function add1(a) {
return a + 1;
}
有一天,我读到一篇网络文章说“在 JavaScript 中,一个函数也是一个值”。
这个事实让我很惊讶,也突破了我的编程技巧。
在那之前,对我来说,价值就是价值,函数就是函数。两者都是不同领域的完全不同的实体。
当然,C#1.0 已经实现了委托,我稍微理解它是关于事件的内部机制的东西。毕竟,C# 一直是主要的 OOP 语言,而且对于函数式编程来说相当丑陋,至少在 version1.0 中是这样。
在 JavaScript 中,函数也是一个值。由于 JavaScript 的函数是一等对象,因此我可以定义一个函数,该函数既可以将其他函数作为参数,也可以将它们作为结果返回。
所以,现在,我写这个:
const add1 = x => x + 1;
const add2 = x => x + 2;
[1, 2, 3].map(add1); //[2,3,4]
[1, 2, 3].map(add2); //[3,4,5]
或者
const plus = (x) => (y => x + y);
plus(1)(5); //6
事实上,这是我在 C# 编程中非常需要的,我一直觉得这是非常错误的。
这就是所谓的函数组合,这是释放编程约束的真正秘诀。
所以,JavaScript 的一个函数是一个一流的对象,它似乎是一个精心设计的通用标准化的构建块,从现在开始,我们称之为“高度可组合的单元”。
一个函数是BEFORE => AFTER
。
基本思想是组合函数。
当您专注于功能组合时,您只关心BEFORE => AFTER
.
当您专注于功能组合时,您应该忘记从代码顶部流向底部或有时循环的流程图。
流程图编码被称为命令式编程,一般来说,它有缺陷且过于复杂。OOP 倾向于成为这种风格。
另一方面,函数式编程会自动将编程风格引入Declarative_programming,而且一般来说,它没有错误或易于调试。
流更难追踪和控制,但组合更容易追踪和控制。程序员不应控制流程,而应编写函数。
3.单子
顺便说一句,我不会在这里使用 Haskell 代码。
对于大多数人来说,理解 monad 事物的一个主要障碍是
- 为了学习 monad,初学者需要熟悉 Haskell 代码和术语。
- 为了熟悉 Haskell 代码和术语,初学者需要学习 Monad。
这是“先有鸡还是先有蛋?” 问题。确保避免。
话虽如此,正如我在本文开头所说的那样,为了分享 Monad 的知识,首先引用“Monad 法则”似乎也很荒谬。
人们只能在他们已经知道的基础上学习。
那么,让我们回到 JavaScript 代码。
函数似乎是高度可组合的单元,但是这个呢?
console.log("Hello world!");
这是最简单的 JS 代码之一,当然它是一个函数。
在 ChromeBrowser 上按 F12 键,然后将代码复制粘贴到开发人员控制台上。
Hello world!
undefined
好的,代码已经完成了显示“Hello world!”的任务。然而,在控制台上,console.log
函数的返回值为undefined
.
编写函数,情况不舒服;一个不舒服的功能。
另一方面,有一个舒适的功能。让我们研究以下代码:
const add1 = x => x + 1;
[1, 2, 3].map(add1); //[2,3,4]
JavaScript 中的数组在函数式编程世界中表现得非常好。
[1, 2, 3].map(add1) //[2,3,4]
表示:
Array
Function=>
Array
函数的输入和输出是同一类型:Array
.
数学结构自始至终是相同的BEFORE => AFTER
。
一致性和同一性的本质是美丽的。
与 USB 接口的耐人寻味的相似性自然引出了一个想法:
Array
Function=>
Array
Function=>
Array
Function=>
Array
...
在 JavaScript 代码中:
[1, 2, 3]
.map(add1) //[2,3,4]
.map(add1) //[3,4,5]
.map(add1);//[4,5,6]
代码提示一旦进入 Array 领域,出口永远是 Array 领域,所以在某种意义上没有出口。
由于数组领域是一个自包含的世界,因此可以在函数式编程中做类似代数的事情。
当我们有:
Array.map(F).map(F).map(F)...
考虑到.map(F)
JavaScript Array 特定的语法,将其替换为更简洁的语法是可能的,例如,通过利用诸如Babel之类的转译器。
所以替换.map(F)
为*F
:
Array*F*F*F...
这看起来像代数。
获得高度可组合的单元,程序员可以写出像代数一样的代码,意义重大,值得非常认真地研究。
在代数中,
a
= 0+a
= 0+0+a
= 0+0+0+a
或者
a
= 1*a
= 1*1*a
= 1*1*1*a
0
在+(加法)运算中,
a + 0 = a //right identity
0 + a = a //left identity
1
在*(乘法)运算中,
a ∗ 1 = a //right identity
1 ∗ a = a //left identity
称为恒等元。
在代数中,
1 + 2 + 3 = 1 + 2 + 3
(1+2) + 3 = 1 + (2+3)
3 + 3 = 1 + 5
6 = 6
称为关联属性
number + number = number
number * number = number
string + string = string
"Hello" + " " + "world" + "!"
= "Hello world" + "!"
= "Hello "+ "world!"
也是关联的,标识元素是""
。
那么,函数式编程中的标识元素是什么?
就像是:
identityF * f = f = f * identityF
函数式编程中的关联属性是什么?
const add1 = x => x + 1;
const add2 = x => x + 2;
const add3 = x => x + 2;
就像是:
add1 * add2 * add3
= (add1 * add2) * add3
= add1 * (add2 * add3)
或者
(add1)(add2)(add3) = (add1)(add2)(add3)
((add1)(add2))(add3) = (add1)((add2)(add3))
(add3)(add3) = (add1)(add5)
(add6) = (add6)
函数式编程是关于函数组合的。
我们在函数式编程中需要的是
function * function = function
当然,在 JavaScript(或其他语言)中,由于每种语言的语法限制,我们不能写出上面的确切形式。
其实我们可以有《Algebraic JavaScript Specification》(JavaScript中常见代数结构的互操作性规范)
那么 JavaScript 数组就是所谓的 Monad 吗?
不,但很接近。JavaScript 数组可以归类为Functor。
Monad是 Functor 的一种特殊形式,具有一些额外的性质(应用了更多规则)。
Functor 仍然是高度可组合的单元之一。
所以我们正在接近 Monad 是什么。让我们更进一步。
现在,我们知道 JavaScript 数组是可以做一些代数的高度可组合的单元之一,至少在一定程度上是这样。
那么除了数组之外的 JavaScript 值呢?功能呢?
学习并遵循Algebraic JavaScript Specification,很容易尝试实现各种可组合单元,包括 Functor 或 Monad,有什么意义?
毕竟,它们只是数学结构的分类表,一味地遵循规范是没有意义的。
4.规格
关键是要获得一个高度可组合的单元,该单元是独立的。这是唯一要满足的规范。
所以,这里是问题的建立:
实现一个生成自包含领域的数学结构,看看它是如何进行的。
什么都好,我会从头开始,但我已经有一个好的模型可以参考。
JavaScript 数组
Array.map(F).map(F).map(F)...
代替 Array 领域,让我们将我的原始M
领域变成这样:
M.map(F).map(F).map(F)...
我认为Array.map
不是简洁的语法,M
它本身就是一个函数:
M(F)(F)(F)...
嗯,总是使用某个预先选择的、普遍标准化的块是一门很好的纪律。这就是开始的想法,所以可能F
也应该是M
:
M(M)(M)(M)...
嗯,这是什么意思??
所以,这是我的疯狂想法。
在函数式编程中,任何函数也是一等对象,这就是突破。所以当我将任何值/对象/函数解释为M
时,就会有另一个突破。
这就像说“任何值都是数组!”一样疯狂。
确切地说,如果它在 JavaScript 领域是疯狂的,但如果它在 Array 的自包含领域是合法的。
因此,我将设计原始M
领域将任何裸值/对象/函数视为M
例如,在M
领域中,当找到裸值:时5
,解释为M(5)
.
换句话说,只要在M
领域中,程序员就不必编写M(5)
,因为5
被隐式解释为M(5)
.
因此,在M
领域:
5
= M(5)
= M(M(5))
= M(M(M(5)))
...
结果,我发现M
有些透明,M
应该是领域中的一个身份元素。
正如我一直在强调的,函数式编程就是组合函数。
函数的组合对于函数式编程是关联的。
M
应该灵活地编写来组合函数 :
const add1 = x => x + 1;
M(10)(add1); //11
M(10)(add1)(add1); //12
M(10)(add1)(add1)(add1); //13
const add2 = M(add1)(add1);
M(10)(add2); //12
const add3 = M(add2)(add1);
M(10)(add3); //13
此外,高阶函数的组合:
const plus = (x) => (y => x + y);
M(plus(1)(5)); //6
M(5)(M(1)(plus)); //6
const plus1 = M(1)(plus);
M(5)(plus1)(; //6
5.实施
这是一个实现M
:
const compose = (f, g) => (x => g(f(x)));
const isMonad = (m) => !(typeof m.val === "undefined");
const M = (m = []) => {
const f = m1 => {
try { //check type error
return M(M(m1).val(m));
} catch (e) {
return M(compose(m, M(m1).val)); // f-f compose
};
};
f.val = m;
return isMonad(m)
? m
: f;
};
M.val = m => m;
记录功能:
const log = (m) => (typeof m !== 'function')
? (() => {
console.log(m);
return m;
})()
: err();
测试代码:
const err = () => {
throw new TypeError();
};
const log = (m) => (typeof m !== 'function')
? (() => {
console.log(m);
return m;
})()
: err();
const loglog = M(log)(log);
M("test")(loglog);
M("------")(log);
M([1])(log);
M(M(M(5)))(log)
M(99)(M)(log)
M("------")(log);
M([1, 2, 3])(([a, b, c]) => [a + 1, b + 1, c + 1])(log)
M("------")(log);
const add1 = a => (typeof a == 'number')
? a + 1
: err();
M(10)(add1)(log); //11
M(10)(add1)(add1)(log); //12
M(10)(add1)(add1)(add1)(log); //13
const add2 = M(add1)(add1);
M(10)(add2)(log); //12
const add3 = M(add2)(add1);
M(10)(add3)(log); //13
M("------")(log);
const plus = (x) => (y => x + y);
M(plus(1)(5))(log); //6
M(5)(M(1)(plus))(log); //6
const plus1 = M(1)(plus);
M(5)(plus1)(log); //6
M("------")(log);
const map = (f) => (array => array.map(f));
const map1 = M(add1)(map);
M([1, 2, 3])(log)(map1)(log);
//===
M("left identity M(a)(f) = f(a)")(log);
M(7)(add1)(log) //8
M("right identity M = M(M)")(log);
console.log(M) //{ [Function: M] val: [Function] }
console.log(M(M)) //{ [Function: M] val: [Function] }
M("identity")(log);
M(9)(M(x => x))(log); //9
M(9)(x => x)(log); //9
M("homomorphism")(log);
M(100)(M(add1))(log); //101
M(add1(100))(log); //101
M("interchange")(log);
M(3)(add1)(log); //4
M(add1)(f => f(3))(log); //4
M("associativity")(log);
M(10)(add1)(add1)(log); //12
M(10)(M(add1)(add1))(log); //12
输出:
test
test
------
[ 1 ]
5
99
------
[ 2, 3, 4 ]
------
11
12
13
12
13
------
6
6
6
------
[ 1, 2, 3 ]
[ 2, 3, 4 ]
left identity M(a)(f) = f(a)
8
right identity M = M(M)
{ [Function: M] val: [Function] }
{ [Function: M] val: [Function] }
identity
9
9
homomorphism
101
101
interchange
4
4
associativity
12
12
好的,工作。
M
是函数式编程中高度可组合的单元。
6.验证
那么,这就是所谓的Monad吗?
是的。
https://github.com/fantasyland/fantasy-land#monad
单子
实现 Monad 规范的值也必须实现Applicative和Chain规范。1.M.of(a).chain(f)
等价于f(a)
(左身份) 2.m.chain(M.of)
等价于m
(右身份)
左恒等式 M(a)(f) = f(a)
M(7)(add1) //8
M(add1(7)) //8
正确的身份 M = M(M)
console.log(M) //{ [Function: M] val: [Function] }
console.log(M(M)) //{ [Function: M] val: [Function] }
适用的
实现 Applicative 规范的值也必须实现Apply规范。1.v.ap(A.of(x => x))
等价于v
(identity) 2.A.of(x).ap(A.of(f))
等价于A.of(f(x))
(homomorphism) 3.A.of(y).ap(u)
等价于u.ap(A.of(f => f(y)))
(interchange)
身份
M(9)(M(x => x)) //9
同态
M(100)(M(add1)) //101
M(add1(100)) //101
互换
M(3)(add1) //4
M(add1)(f => f(3)) //4
链
实现 Chain 规范的值也必须实现Apply规范。1.m.chain(f).chain(g)
等价于m.chain(x => f(x).chain(g))
(关联性)
联想性
M(10)(add1)(add1) //12
M(10)(M(add1)(add1)) //12