monoid_什么是Monoid?

monoid

All the good JS folks are into Functional Programming these days. Many of my readers already know about the Monad, and its other Functor friends. But what about that other similar word that you’ll occasionally see — Monoid. Monoid is a bit more elusive on the web than Monad. You won’t find quite as much good material on the topic. But, they’re still an important part of the category theory of FP.

如今,所有优秀的JS人士都开始从事函数式编程。 我的许多读者已经了解Monad及其其他Functor朋友。 但是,您偶尔会看到的另一个类似单词-Monoid呢? Monoid在网络上比Monad更加难以捉摸。 您不会在该主题上找到太多好的材料。 但是,它们仍然是FP类别理论的重要组成部分。

Monoids encapsulate a binary operation.

Monoid封装了二进制操作。

The way I like to think of a Monoid is the encapsulation of a binary operation. You can concatenate Monoid instances to yield a new Monoid with a merged underlying value. You can also reduce them into their underlying values, much like the Monad unwrap. Let’s try hand-rolling a few Monoids to understand what they’re all about. We’ll start with a fun one that exists for languages that have objects —here’s a JavaScript Assign:

我喜欢想到Monoid的方式是对二进制运算的封装。 您可以连接 Monoid实例以产生具有合并的基础值的新Monoid。 您也可以将它们简化为基本值,就像Monad 展开一样。 让我们尝试手动滚动几个Monoid,以了解它们的全部含义。 我们将从一个有趣的语言开始,该语言针对具有对象的语言而存在-这是一个JavaScript分配:

const VALUE = Symbol.for('value')const Assign = obj ⟹ ({
[VALUE]: { ...obj },
concat(m) { return Assign(Object.assign(obj, m.unwrap()))
unwrap() { return {...this[VALUE]} }
})Assign.empty = () ⟹ Assign({})

Assign takes an object and wraps it up in a nice box. I always hide the underlying values of my data types with a Symbol. Symbols are unique per symbol, like a GUID, and can only be observed with a few special reflective functions or by having access to the Symbol itself (hint: don’t make your symbols user-facing in your codebases). Like I said before, you can concatenate instances of like-monoids together. concat takes a monoid of the same type (in this case Assign) and creates a new one with merged values. It encapsulates a binary object assignment operation. We have to unwrap the supplied Monoid to do this operation, much like chaining Monads. We’ll talk more about the relationship of Monads and Monoids later. I’ve also included an empty constructor for convenience. They’re a bit different from Functors — they don’t map morphisms into their container context; they simply encapsulate one binary function.

Assign接受一个对象并将其包装在一个漂亮的盒子中。 我总是用Symbol隐藏数据类型的基础值。 每个符号的符号都是唯一的,就像GUID一样,并且只能通过一些特殊的反射功能或可以通过访问符号本身来进行观察(提示:请勿使您的符号在代码库中面向用户)。 就像我之前说过的那样,您可以将类似monoid的实例连接在一起。 concat采用相同类型的monoid(在本例中为Assign),并使用合并的值创建一个新的monoid。 它封装了二进制对象分配操作。 我们必须拆开提供的Monoid来执行此操作,就像链接Monad一样。 稍后我们将详细讨论Monads和Monoids的关系。 为了方便起见,我还包括一个empty构造函数。 它们与Functor有点不同-它们不会map态射map到其容器上下文中。 它们只是封装了一个二进制函数。

Notice how I’ve used spread syntax to clone the parameter into our Assign’s underlying value. That’s a safety net due to the fact that Objects in JavaScript are reference types — if you assign an object to two different variables using the = operator, both of those variables will refer to the same object:

请注意,我是如何使用传播语法将参数克隆到Assign的基础值中的。 由于JavaScript中的对象是引用类型,因此这是一个安全网-如果使用=运算符将对象分配给两个不同的变量,则这些变量都将引用同一对象:

//myObj and myObj2 are referentially equalconst myObj = { some: 'prop' }const myObj2 = myObjmyObj.some = 'otherProp'
console.log(myObj2.some) //⟹ 'otherProp'//myObj3 is not referentially equal because we assign a new obj
const myObj3
= { ...myObj }

Thus modifying one variable will modify the referenced object, therefore changing the values for any variable that refers to the object in question. This isn’t good, we want transparent immutable data in FP, so to ensure we don’t mutate a reference I just copy it into a new Object like myObj3 which in turn will reference a different object on the heap.

因此,修改一个变量将修改引用的对象,从而更改任何引用所讨论对象的变量的值。 这不好,我们想要FP中透明的不可变数据,因此为了确保我们不对引用进行突变,我只是将其复制到新对象(如myObj3 ,该对象将依次引用堆上的另一个对象。

So let’s play with Assign a little before I move on to another Monoid.

因此,在继续使用另一个Monoid之前,让我们玩一下Assign。

const base_person = Assign({name: 'Ross'})const concat_person = base_person
.concat(Assign({age: 29})) //⟹ Assign({name: 'Ross', age: 29})

Now let’s try another Monoid — Max. It takes a number value and when concatenated returns the maximum numeric value of the two:

现在,让我们尝试另一个Monoid-Max。 它采用一个数字值,并在连接时返回两者中的最大值:

const Max = x ⟹ ({
[VALUE]: x,
concat(m) { return Max(Math.max(this[VALUE], m.unwrap()) },
unwrap() { return this[VALUE] }
})Max.empty = () ⟹ Max(0)Max(4)
.concat(Max(100)) //⟹ Max(100)Max(42)
.concat(Max.empty()) //⟹ Max(42)

You could easily conceive of a Min, Sum, Product, Quotient, Eq, and probably more Monoids. Practically any binary operation can be turned into a Monoid.

您可以轻松地想到最小,总和,乘积,商,等式以及可能更多的Monoid。 实际上,任何二进制运算都可以转换成Monoid。

This leads us to defining an interface for Monoid! Interfaces, though non-existent in the JavaScript world, are a powerful way to think about problem abstraction in any programming language. And abstraction is key to better programming. Let’s define that interface:

这导致我们为Monoid定义接口! 尽管JavaScript世界中不存在接口,但是接口是思考任何编程语言中问题抽象的有效方法。 抽象是更好编程的关键。 让我们定义该接口:

Monoid<A> {
concat(m: Monoid<A>) ⟶ Monoid<A>
unwrap() ⟶ A
static empty() ⟶ Monoid<A>
}

concat takes a Monoid of the same type as the caller and returns a new Monoid of the same type that represents the merged value of your binary operation. unwrap let’s us into the contents of the Monoidal box, and empty allows us to have a unit constructor. It’s basically equivalent to the identity value that can be represented by that Monoid. So for Sum empty would be 0, Product or Quotient could be 1, Max would be 0 (assuming positive values), Min would be Infinity, and so on. (note: I use static to denote that empty is associated with the constructor rather than with the instances)

concat采用与调用方相同类型的Monoid,并返回相同类型的新Monoid,该新Monoid代表二进制操作的合并值。 unwrap让我们进入Monoidal框的内容,而为empty使我们拥有一个单元构造函数。 它基本上等效于该Monoid可以表示的标识值。 因此,对于“总和”, empty将为0,乘积或商数可能为1,最大值将为0(假设为正值),最小值将为无穷大,依此类推。 (注意:我用 static 表示 empty 与构造函数而不是实例相关)

Eventually, you’ll want to chain these together:

最终,您需要将它们链接在一起:

const result = Product(3)
.concat(Product(4))
.concat(Product(5))
.unwrap() //⟹ 60

But look at all that interspersed function calling and the unwrap at the end. We can do better. Enter mconcat, and mreduce. Some fancy functional names for you. They just mean monoidal concatenation and monoidal reduce, respectively.

但是,请看一下所有散布的函数调用和最后的展开。 我们可以做得更好。 输入mconcatmreduce 。 一些适合您的功能名称。 它们分别意味着单调级联和单调简化。

//mconcat :: (Monoid a ⟶ [a]) ⟶ Monoid a
const mconcat
= (ctor, values) ⟹
values.reduce((acc, v) ⟹ acc.concat(ctor(v)), ctor.empty())//mreduce :: (Monoid a ⟶ [a]) ⟶ aconst mreduce = (ctor, values) ⟹
values.reduce((acc, v) ⟹ acc.concat(ctor(v)), ctor.empty())
.unwrap()mconcat(Product, [3, 4, 5]) //⟹ Product(60)mreduce(Product, [3, 4, 5]) //⟹ 60

They both do basically the same thing, but mconcat leaves the value in the container, while mreduce takes it out. Notice the need for our empty constructor! That empty constructor allows us to use mconcat and mreduce with any Monoid. That’s why it’s part of the Monoid interface.

它们基本上都做相同的事情,但是mconcat将值mconcat在容器中,而mreduce将其删除。 注意需要我们的空构造函数! empty构造函数允许我们将mconcatmreduce与任何Monoid一起使用。 这就是为什么它是Monoid接口的一部分。

You can see how much nicer calling chains of concat looks and feels using our shiny new functions. It’s much more readable than those big groups of concatenations once you understand the meaning of mconcat and mreduce, whose names are the only really odd parts about these functions. Otherwise they’re very straightforward about what they will do.

您可以使用我们闪亮的新功能来查看concat调用链的外观和感觉。 一旦您了解了mconcatmreduce的含义, mconcat的含义就比那些大型的串联mconcat mreduce ,它们的名称是这些函数中唯一真正奇怪的部分。 否则,他们会很直截了当地决定要做什么。

So how does this relate to Monad?

那么这与Monad有何关系?

Let’s use that age old description of Monad that makes everyone go crazy — ‘A Monad is just a Monoid in the category of Functors.’ Well we know about Monoids now and how they encapsulate a binary operation, as well as reduce to their inner value. And we know about Functors and their ability to map. Well, wait…what do you call a functor that can unwrap its contents? Hey, that’s a Monad! That’s how a Monad is a Monoid in the category of Functors. The operation encapsulated is the application of morphisms (functions) over the content of the container context. And instead of concat, now we call it map. And I think map is fitting — its name suggests that you may get a container with a different type, or a mapping from type a to type b.

让我们使用对Monad的古老描述使每个人都发疯-“ Monad只是Functors类别中的Monoid。” 好了,我们现在了解Monoid,以及它们如何封装二进制运算以及如何降低其内部值。 并且我们知道函子及其map 。 好吧,等等……您如何称呼可以展开其内容的函子? 嘿,那是Monad! 这就是Monad在Functors类别中的Monoid的方式。 封装的操作是在容器上下文的内容上应用态射(函数)。 现在我们将其称为concat map ,而不是concat 。 而且我认为map很合适-它的名称表明您可能会得到一个具有不同类型的容器,或者从a类型到b类型a 映射

I hope this article helps clear up exactly what a Monoid is, as well as how it relates to the Monad. Until next time, FP on, friends!

我希望本文能帮助您弄清什么是Monoid,以及它与Monad的关系。 直到下次,FP继续前进,朋友们!

翻译自: https://medium.com/swlh/what-the-heck-is-a-monoid-fb8b7d267fda

monoid

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值