monad_Monad界面

monad

Monads are all-the-rage these days in the JS world. I’d be surprised if any serious JS developer hadn’t heard of Monads. But just in case you haven’t, I like to describe a monad as a fancy box. It’s a special box. You can’t (well, you shouldn’t be able to) see exactly what’s inside the box, but you can know that this box holds values of a certain type, and exposes three primary operators with which we can modify or take out the content of that box. Those operations are what I call the Monad Interface.

这些天来, Monad在JS世界中风靡一时。 如果没有任何认真的JS开发人员听说Monads,我会感到惊讶 但是以防万一,我想将monad描述为一个精美的盒子。 这是一个特殊的盒子。 您无法(嗯,您应该)看到框内的内容,但是您可以知道该框保存了某种类型的值,并公开了三个主要的运算符,我们可以使用它们来修改或删除该盒子的内容。 这些操作就是我所说的Monad接口。

OK, so Monad isn’t exactly an interface. But in JS we can think of it as one. As a quick refresher, in a strongly typed language (such as the C family) an interface is the definition of what a class object has. If you’ve ever used TypeScript, you know that an interface describes the expected shape of something — like an object, or the parameters and return value of a function. What an interface boils down to is a contract to be upheld by anything that implements that interface, but it gives no specifics of the implementation. It’s useful to think of things as interfaces, because interfaces are abstract. Abstraction is one of the most useful concepts in computer science. To say something is abstract is to express how far away it is from machine code. The further away from machine code we can be the better because humans brains understand the abstraction of languages; only machines are made to read machine code. The best part about an interface is that it is a cost-free abstraction. If you write one in TypeScript, for example, it doesn’t even generate code in the end, it only compile-time checks that the interface’s contract is upheld. In bare JS, an interface is only in the mind of the author. So we can only uphold an interface’s contract by assuring on our own that all of the expected methods and fields are correctly implemented — there are no compile time checks to help you out. But that’s OK, because the dynamically-typed nature of JS comes with some other very nice benefits, which I won’t necessarily discuss just this moment.

好的,所以Monad并非完全是一个界面。 但是在JS中,我们可以将其视为一体。 作为快速入门,使用强类型语言(例如C系列), 接口是对类对象的定义 如果您曾经使用过TypeScript,那么您就会知道接口描述了某种东西的预期形状 ,例如对象,或者函数的参数和返回值。 接口归结为要由实现该接口的任何事物所遵守的合同 ,但是它没有提供实现的细节。 将事物视为接口很有用,因为接口是抽象的。 抽象是计算机科学中最有用的概念之一。 说些抽象的东西就是表达它与机器代码的距离。 距离机器代码越远,我们越好,因为人类的大脑可以理解语言的抽象 仅使机器读取机器代码。 关于接口最好的部分是它是一种免费的抽象。 例如,如果您使用TypeScript编写代码,那么它甚至最终都不会生成代码,它只会在编译时检查接口的合同是否得到遵守。 在裸露的JS中,接口只在作者的脑海中。 因此,我们只能通过确保自己正确地实现了所有预期的方法和字段来维护接口的合同-没有编译时检查可以帮助您。 但这没关系,因为JS的动态类型化特性还具有其他一些非常好的好处,我现在不必在此进行讨论。

So without further ado, here is the interface that Monad adheres to:

因此,事不宜迟,这是Monad遵循的界面:

Monad<A> { 
map(f: Function<A> ⟶ B ) ⟶ Monad<B>
unwrap() ⟶ A
chain(f: Function<A> ⟶ Monad<B>) ⟶ Monad<B>
}

It’s actually not that complicated. I’ll go through each of these three functions and make them crystal clear.

实际上并不那么复杂。 我将逐一介绍这三个功能,并使其清晰明了。

First we’ll start with map. map is the easiest function to grasp of the Monad interface. We use it all the time in JS. It states, ‘Given a monad M a and a function a -> b, apply that function to the contents of monad M a producing a monad M b. You see map in the wild in native JS’s Array type. In Array, map simply applies the provided function to each element in the array, producing a new array:

首先,我们从map开始。 map是掌握Monad界面最简单的功能。 我们一直在JS中使用它。 它指出,“给定一个单子M a和功能a -> b ,应用功能单子的内容M a生产单子M b 。 您会以原生JS的Array类型随意查看地图。 在Array中map只是将提供的函数应用于数组中的每个元素,从而生成一个新的数组:

const sq = x => x * xconst a1 = [1, 2, 3]const a2 = a1.map(sq) //⟹ [1, 4, 9]

Notice the way I call map with just the function sq's identifier — that’s a declarative style of coding. You define things in one place and call them by name elsewhere. It actually makes things much less complicated to understand to call them simply by name, because it de-clutters the code you write. Plus, you might want to call sq in more than one place. Why write out the anonymous function x => x * x every time you want to call sq? Also see how nice that is to read? It’s nearly plain English, ‘Apply function sq to the contents of a1 and assign that to a new array a2’.

注意,我仅使用函数sq的标识符来调用map的方式-这是一种声明性的编码方式。 您可以在一处定义事物,而在其他地方以名称命名。 实际上,仅通过名称来调用它们就使事情变得不那么复杂,因为它会使您编写的代码变得混乱。 另外,您可能想在多个地方打电话给sq 。 为什么每次要调用sq都要写出匿名函数x => x * x ? 还看到它读起来有多好? 它几乎是英文,“将函数sq应用于a1的内容,并将其分配给新数组a2 ”。

Now for unwrap. unwrap is the most pointless-seeming of the Monad interface. You may know it as flat, flatten, join, emit, and probably others, but all it means is, ‘Take the contents out of the box’. Although it may seem like a silly thing to have, it is actually a really important function. If you couldn’t take the inner content out of the box, Monads would be pretty useless. That being said, you shouldn’t normally be calling unwrap, and instead should opt for a function that considers every variant of your Monad.

现在开始unwrapunwrapMonad界面中最无意义的东西。 您可能知道它为flatflattenjoinemit ,也许还有其他名称,但是它的意思是“将内容从包装箱中取出”。 尽管它看起来似乎很愚蠢,但实际上是一项非常重要的功能。 如果您不能直接使用内部内容,那么Monads将毫无用处。 话虽这么说,您通常不应该调用unwrap ,而是应该选择一个考虑Monad每种变体的函数。

Consider this snippet, using functions that I wrote for another article, Pattern Matching in JS Pt 1, 2, and 3. (Look at the codepen example at the end of part three for the completely defined functions)

考虑这个片段中,使用的是我写的另一篇文章中,模式匹配JS PT功能12 ,和3(有关完整定义的功能,请参见第三部分末尾的codepen示例)

const Option = sum_type('Option', {
Some(value){ return { value } },
None() { }
})const { Some, None } = Optionconst myOption = Some(3)const result = match(myOption, {
Some: ({value}) ⟹ `got Some(${value})
None: () ⟹ `got None`
})console.log(result) //⟹ 'Some(3)'

That snippet creates the basis of the Sum Type and Monad called Option. It isn’t a full fledged Monad yet, because we haven’t defined map, chain, or unwrap on it. If we had unwrap defined then we could take out our inner value using that function. But instead I’m using a special unwrapping method that I’ve called match. match considers every variant of a registered Sum Type (the logic of which occurs in the sum_type function) and finds if the provided instance matches any of the provided patterns. This is preferential to unwrap because unwrapping a null value will lead to potential null reference or undefined errors. While my match method is specifically for Sum Types (which Monads aren’t necessarily, but may be), most Monads have a specific function for unwrapping them safely, considering null or undefined values and responding appropriately and, more importantly, predictably.

该片段创建了Sum Type和Monad的基础,即Option 。 它还不是完整的Monad,因为我们尚未在其上定义mapchainunwrap 。 如果定义了拆包,则可以使用该函数提取内部值。 但是相反,我使用一种称为match的特殊解包方法。 match考虑注册的Sum Type的每个变体(其逻辑发生在sum_type函数中),并查找所提供的实例是否与所提供的任何模式匹配 。 这优先于unwrap因为解包null值将导致潜在的null引用或未定义的错误。 虽然我的match方法专门用于求和类型(不一定是Monad,但可能是Monad),但是大多数Monad具有特定的功能,可以安全地展开它们,考虑空值或未定义的值,并做出适当的响应,更重要的是可预测地做出响应

Let’s flesh out Option with map and unwrap operators as we defined above. I’ll also have to define a constructor function on Option to make a full example.

让我们通过上面定义的mapunwrap运算符unwrap Option 。 我还必须在Option上定义一个构造函数,以构成完整的示例。

(note: I will be using extend which is another function defined in my series on pattern matching, see the link above and maybe open the example on codepen to see everything that I’m leaving out here)

(注意:我将使用 extend ,这是我在模式匹配系列中定义的另一个函数,请参见上面的链接,也可以在codepen上打开示例以查看此处遗漏的所有内容)

Option.of = x ⟹ 
x ≡ null
|| x ≡ undefined
? None()
: Some(x)const isSome = x ⟹ x[TAG] && x[TAG] ≡ 'Some'const isNone = x ⟹ x[TAG] && x[TAG] ≡ 'None'extend(Some.prototype, {
map(f) { return Option.of(f(this[SECRET].value)) },
unwrap() { return this[SECRET].value }
})extend(None.prototype, {
map(f) { return this },
unwrap() { return this }
})

Now we’ve got some real functionality. Let’s quickly discuss what this means:

现在我们有了一些实际功能。 让我们快速讨论一下这意味着什么:

  • Option has two variants — Some and None.

    Option有两个变体-SomeNone

  • You can either have Some of something, or nothing at all.

    您可以拥有某些东西,或者一无所有。
  • When you have Some of something, you can map and unwrap it just like normal.

    当您拥有某些东西时,可以像平常一样映射和展开它。
  • When you have None of something you can try to map or unwrap but you still just have None, so that’s why we keep returning it.

    当您没有任何东西时,您可以尝试映射或解包,但您仍然只有None,这就是我们不断返回它的原因。

Now we can map functions over the contents of our box. If we stopped at just map, we’d have what is referred to as a Functor. A Functor is a fancy box that can map. It’s easy to mistake a Functor for a full-fledged Monad, but they are different. There are mathematical Identity laws that must be upheld to properly define a a construct as a Monad, involving both the unwrap and chain methods. We won’t exactly get into all of that, but we will define chain, which when defined properly along with unwrap pretty much does make a Functor a Monad. Let’s define chain now:

现在,我们可以在框的内容上map函数。 如果我们在map停下来,我们会得到所谓的Functor 。 函子是可以map的精美盒子。 将Functor误认为是成熟的Monad很容易,但是它们是不同的。 必须坚持数学身份定律,以正确地将一个结构定义为Monad,同时涉及到unwrapchain方法。 我们不会完全涉及所有这些内容,但是我们将定义chain ,当正确定义chainunwrap时,它确实会使Functor成为Monad。 现在定义chain

extend(Some.prototype, {
map(f) { return Option.of(f(this[SECRET].value)) },
unwrap() { return this[SECRET].value },
chain(f) { return this.map(f).unwrap() }
})extend(None.prototype, {
map(f) { return this },
unwrap() { return this },
chain(f) { return this }
})

See why I saved chain for last? It’s given to us for free by defining map and unwrap on a type. chain is really for chaining together functions that lift values into Monads, discarding the outer Monad by unwrapping. It’s a kind of mathematical symmetry that allows us to apply monad-constructing operations without getting a box-in-a-box-in-a-box kind of situation. Those are the types of complexities FP aims to avoid.

看看为什么我最后保存了锁链? 它是通过定义mapunwrap类型免费提供给我们的。 chain实际上是用于将函数提升到Monad的功能链接在一起,通过展开将外部Monad丢弃。 这是一种数学对称性,它使我们能够应用monad构造操作,而无需遇到“盒子里放着盒子”的情况。 FP旨在避免这些复杂性。

To illustrate how chain works let’s define another Sum Type Monad that is really similar to Option. It’s called Either:

为了说明链的工作原理,让我们定义另一个与Option类似的Sum Type Monad 它称为Either:

const Either = sum_type('Either', {
Right(value) { return { value } },
Left(value) { return { value } }
})const { Right, Left } = EitherEither.of = x ⟹ Right(x)const left = x ⟹ Left(x)extend(Right.prototype, {
map(f) { return Either.of(f(this[SECRET].value)) },
unwrap() { return this[SECRET].value },
chain(f) { return this.map(f).unwrap() },
})extend(Left.prototype, {
map(f) { return this },
unwrap() { return this[SECRET].value },
chain(f) { return this },
})

Either is frequently used as an error handling type of Monad. Whatever is in a Right may carry on being operated on, but a Left will stop operations and return whatever was lifted into the Left.

两种都经常用作Monad的错误处理类型。 右边的任何东西都可以继续进行操作,但是左边的将停止操作并返回抬起的所有东西。

Now we can play with chain a bit:

现在我们可以使用chain了:

//prop :: String ⟶ Object ⟶ Option aconst prop = name ⟹ target ⟹ Option.of(target[name])//prop :: Object ⟶ Option aconst isProgrammer = obj ⟹ prop('programmer')(obj)//Right Object
const person
= Either.of({
name: 'Ross',
age: 29,
programmer: true
})//chains from Right({person}) ⟶ Some({person})console.log(isSome(person.chain(prop('name')))) //⟹ true//chains from Right({person}) ⟶ Noneconsole.log(isSome(person.chain(prop('profession')))) //⟹ falseconsole.log(isSome(person.chain(isProgrammer))) //⟹ true

If we didn’t have chain, we could still map functions like this. The problem with that approach is that then we end up with an Either(Option(A)) instead of just an Option(A). It’s much more complicated to work with boxes in boxes. Let’s just look at what would happen:

如果我们没有chain ,我们仍然可以映射这样的函数。 这种方法的问题在于,我们最终得到一个Either(Option(A))而不是一个Option(A) 。 使用盒子中的盒子要复杂得多。 让我们看看会发生什么:

const myname = person.map(prop('name')) //⟹ Right(Some('Ross'))let result = match(myname, {
Right: ({value}) ⟹ match(value, {
Some: ({value}) ⟹ `got Some(${value})`,
None: () ⟹ `got None`
}),
Left: ({value}) ⟹ `got Left(${value})`
})

See how we now have to consider what our nested values are, and then add more and more match cases as we go? That’s why chain is so very important — it makes an otherwise fairly complex series of operations very simple and easy to understand. One can only process so much information at a time, so why not make life better for yourself by making what your brain needs to process easier? It just makes sense. Imagine trying to process some retrieved JSON using a method like prop that lifts the result into a new monad — every nested value creates a new Monadic wrapper if we don’t use chain to unwrap each result.

看看我们现在如何考虑嵌套值是什么,然后在进行时添加越来越多的匹配用例? 这就是为什么chain如此重要的原因-它使原本相当复杂的一系列操作变得非常简单和易于理解。 一个人一次只能处理这么多的信息,那么为什么不通过使大脑需要处理的事情变得更轻松来使自己的生活变得更好呢? 这很有意义。 想象一下尝试使用像prop这样的方法处理一些检索到的JSON,将结果提升到一个新的monad中-如果我们不使用链来解包每个结果,则每个嵌套值都会创建一个新的Monadic包装器。

Anyway, now we know how to construct a Monad based on this simple interface. Here’s an extension of the Pattern Matching in JS pen that shows examples for Option and Either:

无论如何,现在我们知道了如何基于这个简单的界面构造Monad。 这是JS笔中的模式匹配的扩展,其中显示了Option和Either的示例:

Hope you enjoyed another article about functional concepts, stay tuned for more!

希望您喜欢另一篇有关功能概念的文章,敬请期待!

翻译自: https://medium.com/swlh/the-monad-interface-9695179185e4

monad

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值