JavaScript中的Monads

Raj*_*jat 19 javascript monads

所以我想了解JavaScript中monad有用的实际案例.

我在JavaScript上阅读了关于Monads的文章,并了解jQuery是其使用的一个例子.但除了"链接"模式,使用Monads在前端工程中可以有效解决哪些其他问题?

参考:

http://importantshock.wordpress.com/2009/01/18/jquery-is-a-monad/

http://igstan.ro/posts/2011-05-02-understanding-monads-with-javascript.html

小智 8

这是我尝试为您可能从未在其他地方找到过的monads入门者做出的贡献。

monad是功能编程中的高度可组合的单元(一种编程的构建块)。

(IMO,在没有任何上下文和合理化的情况下引入“ Monad法则”仅是毫无用处的分类,并且是理解该概念的危险。不用担心,我将在本文的后面做此工作。)

在大多数情况下,我们有许多种编程的构建块,例如对象,函数,列表等。

尽管拥有各种各样的编程块似乎是自然法则,并且出于实际目的进行灵活编程是不可避免的,但事实是,拥有各种各样的编程块是编程环境污染的主要来源之一。

通过使用各种块来构建块是一项复杂的任务。在每种情况下,程序员都必须非常明智地从各种块中选择一个块,并且很长一段时间内,他将失败。

因此,不建议根据情况选择块的种类,相反,始终使用普遍标准化的特定预选块是一个很好的习惯。

实际上,这种智慧在当今的PC世界中很普遍。

USB是通用串行总线的简称,是一种工业标准,旨在定义电缆,连接器和协议,用于个人计算机及其外围设备之间的连接,通信和电源。

获得设计良好的通用标准化构建基块可以消除许多问题。

  1. 对象是(过去是)一个。
  2. 功能是其中之一。
  3. 莫纳德就是那个。
  4. 技术指标
  5. 实作
  6. 验证

1.面向对象

面向对象编程(OOP)是基于“对象”概念的编程范例,其中可能包含字段形式的数据,通常称为属性;以及程序形式的代码,通常称为方法。对象的一个​​特征是对象的过程可以访问并经常修改与它们关联的对象的数据字段(对象的概念为“ this”或“ self”)。在OOP中,计算机程序是通过使它们脱离相互交互的对象而设计的。OOP语言种类繁多,但是最受欢迎的是基于类的,这意味着对象是类的实例,通常也确定它们的类型。

选择对象作为通用标准化的构建块时,程序员将准备一个包含成员值和函数的基类,并且为了获得块的变体,将使用继承

OOP的想法通常是通过使用现实世界的物理对象来解释的,并且范式本身在数学抽象上是薄弱的。

例如,函数(或方法)从属于对象,而函数不必是一流的对象,这是理所当然的,因为范式最初选择的对象是其设计良好的通用标准化构造块。

功能在哪里是对象的从属实体(作为标准化的构建基块,并且两个角色完全不同)的观点来自物理世界中的工程学意义。而不是编程实际所在的数学抽象。

OOP的根本问题只是对象原来不是设计良好的通用标准化构件。具有强大数学背景的函数式编程或monad是更好的选择。

2.功能编程

函数式编程就是关于函数的组合。

说起来很容易,但这是编程历史上的成就。

我不想研究悠久的编程历史,而是分享自己的个人历史。

从1.0版开始,我曾经是C#(OOP)程序员,总的来说,我很满意,但是觉得有些不对劲,但是不知道那是什么。

后来我成为了JavaScript程序员,在早期,我曾经这样写:

function add1(a) {
    return a + 1;
}
Run Code Online (Sandbox Code Playgroud)

有一天,我读了一些网络文章,说“在JavaScript中,函数也是一个值”。

这个事实令我感到惊讶,并突破了我的编程技能。

在那之前,对我来说,很明显,价值就是价值,而功能就是功能。两者都是在不同领域中绝对不同的实体。

当然,C#1.0已经实现了委托,并且我稍微理解这与事件的内部机制有关。毕竟,C#一直是一种主要的OOP语言,至少在版本1.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]
Run Code Online (Sandbox Code Playgroud)

要么

const plus = (x) => (y => x + y);
plus(1)(5); //6
Run Code Online (Sandbox Code Playgroud)

实际上,这是我在C#编程中急需的,这是我感到非常错误的事情。

这称为函数组合,这是释放编程约束的真正秘密。

因此,JavaScript的功能是一个一流的对象,并且它似乎是一个设计良好的通用标准化构建块,从现在开始,我们将其称为“高度可组合的单元”。

一个功能是BEFORE => AFTER

基本思想是组成功能。

当您专注于功能组成时,您只关心的各种组成BEFORE => AFTER

当您专注于功能组合时,您应该忘记流程图,它从代码的顶部到底部流动,有时甚至循环。

流程图编码被称为命令式编程,通常来说,它有错误并且过于复杂。OOP倾向于成为这种样式。

另一方面,函数式编程会自动将编程风格引向Declarative_programming,并且通常来说,它不是bug也不容易调试。

流程更难追踪和控制,但是组合更容易追踪和控制。程序员不应控制流程,而应组成功能。

3.单子

顺便说一句,我将不在这里使用Haskell代码。

对于大多数人而言,理解单子事物的主要障碍是

  1. 为了学习monad,初学者需要熟悉Haskell代码和术语。
  2. 为了适应Haskell代码和术语,初学者需要学习Monad。

这是“先吃鸡还是先吃鸡蛋?” 问题。确保避免。

话虽如此,正如我在本文开头所述,要分享Monad的知识,首先引用“ Monad法律”似乎也很荒谬。

人们只能根据已经知道的知识来学习。

因此,让我们回到JavaScript代码。

函数似乎是高度可组合的单元,但是呢?

console.log("Hello world!");
Run Code Online (Sandbox Code Playgroud)

这是最简单的JS代码之一,并且肯定是一个函数。

在ChromeBrowser上按F12键,然后将代码复制并粘贴到开发人员控制台上。

Hello world!
undefined
Run Code Online (Sandbox Code Playgroud)

好的,代码已完成显示“ Hello world!”的任务。但是,在控制台上,该console.log函数的返回值为undefined

组成职能时,情况令人不舒服;令人不舒服的功能

另一方面,有一个舒适的功能。让我们研究以下代码:

Hello world!
undefined
Run Code Online (Sandbox Code Playgroud)

JavaScript中的数组在函数式编程世界中表现良好。

const add1 = x => x + 1;
[1, 2, 3].map(add1); //[2,3,4]
Run Code Online (Sandbox Code Playgroud)

表示:
Array Function=> Array

函数的输入和输出是同一类型:Array

整个数学结构是相同的BEFORE => AFTER

一致性和身份的本质是美丽的。

与USB接口的有趣相似之处自然会带来一个想法:
Array Function=> Array Function=> Array Function=> Array...

在JavaScript代码中:

[1, 2, 3].map(add1)   //[2,3,4]
Run Code Online (Sandbox Code Playgroud)

该代码建议,一旦您进入Array域,退出将始终是Array域,因此在某种意义上没有退出。

由于数组领域是一个独立的世界,因此可以在函数式编程中做类似代数的事情。

当我们有:

[1, 2, 3]
  .map(add1) //[2,3,4]
  .map(add1) //[3,4,5]
  .map(add1);//[4,5,6]
Run Code Online (Sandbox Code Playgroud)

考虑到.map(F)JavaScript数组特定的语法,例如,可以利用Babel之类的编译器,将其替换为更简洁的语法。

因此,替换.map(F)*F

Array.map(F).map(F).map(F)...
Run Code Online (Sandbox Code Playgroud)

这看起来像代数

获得高度可组合的单元后,程序员可以编写代数这样的代码,这意味着意义重大,值得认真研究。

在代数中

a
= 0+a
= 0+0+a
= 0+0+0+a
Run Code Online (Sandbox Code Playgroud)

要么

a
= 1*a
= 1*1*a
= 1*1*1*a
Run Code Online (Sandbox Code Playgroud)

0 在+(加法)操作中,

a + 0 = a  //right identity
0 + a = a  //left identity
Run Code Online (Sandbox Code Playgroud)

1 在*(乘法)运算中,

a ? 1 = a  //right identity
1 ? a = a  //left identity
Run Code Online (Sandbox Code Playgroud)

被称为身份元素

在代数中

1 + 2 + 3 = 1 + 2 + 3
(1+2) + 3 = 1 + (2+3)
    3 + 3 = 1 + 5
        6 = 6
Run Code Online (Sandbox Code Playgroud)

被称为关联财产

number + number = number

number * number = number

string + string = string

"Hello" + " " + "world" + "!" 
= "Hello world" + "!" 
= "Hello "+ "world!"
Run Code Online (Sandbox Code Playgroud)

也是关联的,标识元素是""

那么,函数式编程中的标识元素是什么?

就像是:

identityF * f = f = f * identityF
Run Code Online (Sandbox Code Playgroud)

函数编程中的关联属性是什么?

const add1 = x => x + 1;
const add2 = x => x + 2;
const add3 = x => x + 2;
Run Code Online (Sandbox Code Playgroud)

就像是:

add1 * add2 * add3
= (add1 * add2) * add3
= add1 * (add2 * add3)
Run Code Online (Sandbox Code Playgroud)

要么

 ?(add1)(add2)(add3) = (add1)(add2)(add3)
 ((add1)(add2))(add3) = (add1)((add2)(add3))
         (add3)(add3) = (add1)(add5)
?             (add6) = (add6)
Run Code Online (Sandbox Code Playgroud)

函数式编程全部与函数组成有关。

我们在函数式编程中需要的是

function * function = function
Run Code Online (Sandbox Code Playgroud)

当然,由于每种语言的语法限制,在JavaScript(或其他语言)中,我们无法编写上面的确切形式。

实际上,我们可以拥有“代数JavaScript规范”(JavaScript中常见代数结构的互操作性规范)

代数JavaScript规范

那么JavaScript数组就是所谓的Monad吗?

不,但是很近。JavaScript数组可以归类为Functor

Monad是Functor的一种特殊形式,具有一些额外的性质(适用更多规则)。

函子仍然是高度可组合的单元之一。

因此,我们正在接近Monad。让我们走得更远。

现在,我们知道JavaScript数组是可组合的单元之一,可以至少在一定程度上进行代数运算。

那么除了数组以外的JavaScript值又如何呢?函数呢?

学习并遵循代数JavaScript规范,很容易尝试实现各种可组合单元,包括Functor或Monad,这有什么意义?

毕竟,它们只是数学结构的分类表,盲目地遵循规范是没有意义的。

4.规格

关键是要获得一个高度可组合的单元,领域是独立的。这是唯一需要满足的规范。

因此,这是问题的建立:
实现一个数学结构,该结构生成一个独立的领域,并查看其发展过程。

一切都很好,我将从头开始,但是我已经有一个很好的模型可供参考。

JavaScript数组

Array.map(F).map(F).map(F)...
Run Code Online (Sandbox Code Playgroud)

代替Array领域,让我们M像这样创建我的原始领域:

M.map(F).map(F).map(F)...
Run Code Online (Sandbox Code Playgroud)

我认为Array.map这不是一个简洁的语法,M它本身是一个函数:

M(F)(F)(F)...
Run Code Online (Sandbox Code Playgroud)

好吧,总是使用统一标准化的某些预选块是一个很好的准则。那是开始的想法,所以大概F也应该是M

M(M)(M)(M)...
Run Code Online (Sandbox Code Playgroud)

嗯,这是什么意思?

所以,这是我的疯狂主意。

在函数式编程中,任何函数也是一流的对象,这就是突破。因此,当我将任何值/对象/函数解释为时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)))
...
Run Code Online (Sandbox Code Playgroud)

结果,我发现它M是透明的,M应该成为领域中的标识元素。

正如我一直强调的那样,函数式编程完全是关于组合函数的。

函数的组成与函数编程相关。

M应灵活写入撰写功能:

Array*F*F*F...
Run Code Online (Sandbox Code Playgroud)

另外,高阶函数的组成:

a
= 0+a
= 0+0+a
= 0+0+0+a
Run Code Online (Sandbox Code Playgroud)

5,实施

这是的实现M

a
= 1*a
= 1*1*a
= 1*1*1*a
Run Code Online (Sandbox Code Playgroud)

记录功能:

a + 0 = a  //right identity
0 + a = a  //left identity
Run Code Online (Sandbox Code Playgroud)

测试代码:

a ? 1 = a  //right identity
1 ? a = a  //left identity
Run Code Online (Sandbox Code Playgroud)

输出:

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
Run Code Online (Sandbox Code Playgroud)

好,工作了

M 是函数编程中高度可组合的单元。

6.验证

那么,这就是所谓的Monad吗?

是。

https://github.com/fantasyland/fantasy-land#monad

单子

实现Monad规范的值还必须实现ApplicativeChain规范。1. M.of(a).chain(f)等同于f(a)(左身份)2. m.chain(M.of)等同于m(右身份)

左恒等式M(a)(f)= f(a)
1 + 2 + 3 = 1 + 2 + 3
(1+2) + 3 = 1 + (2+3)
    3 + 3 = 1 + 5
        6 = 6
Run Code Online (Sandbox Code Playgroud) 右身份M = M(M)
number + number = number

number * number = number

string + string = string

"Hello" + " " + "world" + "!" 
= "Hello world" + "!" 
= "Hello "+ "world!"
Run Code Online (Sandbox Code Playgroud)

适用性

实现应用规范的值也必须实现应用规范。1. v.ap(A.of(x => x))等同于v(identity)2. A.of(x).ap(A.of(f))等同于A.of(f(x))(同态)3. A.of(y).ap(u)等同于u.ap(A.of(f => f(y)))(互换)

身份
identityF * f = f = f * identityF
Run Code Online (Sandbox Code Playgroud) 同态
const add1 = x => x + 1;
const add2 = x => x + 2;
const add3 = x => x + 2;
Run Code Online (Sandbox Code Playgroud) 互换
add1 * add2 * add3
= (add1 * add2) * add3
= add1 * (add2 * add3)
Run Code Online (Sandbox Code Playgroud)

实现Chain规范的值还必须实现Apply规范。1. m.chain(f).chain(g)等于m.chain(x => f(x).chain(g))(缔合性)

关联性
 ?(add1)(add2)(add3) = (add1)(add2)(add3)
 ((add1)(add2))(add3) = (add1)((add2)(add3))
         (add3)(add3) = (add1)(add5)
?             (add6) = (add6)
Run Code Online (Sandbox Code Playgroud)

  • 很棒的帖子,谢谢!有点失望的是,您必须在 monad 实现中对函数使用突变,恕我直言,这使得它更难阅读,并且与函数式编程哲学不太一致。难道不能使用更明确的对象(例如“{val,function}”)来完成此操作吗? (2认同)

guy*_*abi 7

好吧,我认为第一篇文章非常精彩且非常详细.它描述了JQuery及其monad性质解决的许多问题.

  1. JQuery包装DOM元素并提供更丰富的界面.解决的问题很多:更丰富的事件("mouseenter","mouseleave","hashchnged"等).事件绑定添加处理程序而不是覆盖.CSS处理的接口类似于JQuery公开的其他接口.

这也是JQuery对很多开发人员如此直观的原因,因为它简单地包装了我们所知道的并且不会尝试重新发明HTML.

更不用说它在引用null时会节省很多错误.如果我没有带id的元素guy,那么运行$("#guy").text("I am not here")不会在JQuery中导致错误.

  1. JQuery轻松地围绕DOM元素包装,允许在原始JS和JQuery的界面之间往返.这允许开发人员按照自己的进度学习JQuery,而不是一次性重写整个代码.

  2. 当JQuery使用参数提供回调时,它使用DOM对象而不是JQuery的包装器.这允许第三方轻松地与JQuery集成,因为他们不需要依赖JQuery.例如,假设我编写了一个使用原始JavaScript绘制红色文本的函数.function paintRed(element){element.style.color="red"} - 我可以轻松地将此函数作为回调函数传递给JQuery函数.

  • Monad 模式如何导致“更丰富的事件(“mouseenter”、“mouseleave”、“hashchnged”等)”? (2认同)
  • Monad 不会导致更丰富的事件。没有它你可以拥有一个 Monad,你可以在没有 Monad 的情况下拥有更丰富的事件。然而,包装 DOM 是一种 monad 质量。通过提供包装器而不是直接的 DOM 访问,您可以增强行为。 (2认同)