0

我从Jorge Castillo本人的这篇文章中读到了关于Reader Monad的内容,并且我也得到 了Paco的这篇文章。似乎两者都以不同的方式解决了这个想法。(或者我错了吗?)Dependency Injection

我真的很困惑我是否理解整个Reader Monad以及它与Simple Depenency InjectionPaco谈论的内容之间的关系。

谁能帮我理解这两件事?根据情况,我会在一个项目中同时需要它们吗?

4

1 回答 1

2

您的怀疑是可以理解的,因为是的,两种方法都有相同的结果:在调用堆栈中一直为您隐式传递依赖关系,因此您不需要在每个级别显式传递它们。使用这两种方法,您将从外部边缘传递一次依赖项,仅此而已。

假设您有函数 a()、b()、c() 和 d(),假设每个函数调用下一个函数:a() -> b() -> c() -> d() . 那就是我们的计划。

如果您没有使用任何上述机制,并且您需要 d() 中的一些依赖项,那么您最终会在每个级别上一直转发您的依赖项(我们称之为 ctx):

a(ctx) -> b(ctx) -> c(ctx) -> d(ctx)

在使用上述两种方法中的任何一种后,它会像:

a(ctx) -> b() -> c() -> d()

但是,重要的是要记住,您可以在每个函数的范围内访问您的依赖项。这是可能的,因为使用所描述的方法,您可以启用一个封闭的上下文,该上下文会在每个级别上自动转发它们,并且每个函数都在其中运行。因此,在该上下文中,该函数可以获得这些依赖项的可见性。

读者:这是一种数据类型。我鼓励您阅读并尝试理解这个解释数据类型的词汇表,因为这两种方法之间的区别需要了解什么是类型类和数据类型,以及它们如何协同工作:

https://arrow-kt.io/docs/patterns/glossary/

总而言之,数据类型代表程序数据的上下文。在这种情况下,Reader 代表需要一些依赖项才能运行的计算。即像 (D) -> A 这样的计算。由于它的 flatMap / map / 和它的其他函数以及它们的编码方式,D 将在每个级别上隐式传递,并且由于您将每个程序函数定义为阅读器,您将始终在阅读器上下文中操作,因此可以访问所需的依赖项(ctx)。IE:

a(): Reader<D, A>
b(): Reader<D, A>
c(): Reader<D, A>
d(): Reader<D, A>

因此,将它们与 Reader 可用的组合器(如 flatMap 或 map)链接起来,您将得到 D 一直隐式传递并为每个级别启用(可访问)。

另一方面,Paco 的帖子描述的方法看起来不同,但最终实现了相同的效果。这种方法是关于利用 Kotlin 扩展函数,因为通过定义一个程序来处理所有级别的接收器类型(我们称之为上下文)将意味着每个级别都可以访问所提到的上下文及其属性。IE:

Context.a()
Context.b()
Context.c()
Context.d()

请注意,扩展函数接收器是一个参数,如果没有扩展函数支持,您需要在每次调用时手动作为附加函数参数传递,因此这种方式是函数需要运行的依赖项或“上下文”。以这种方式理解这些并理解 Kotlin 如何解释扩展函数,接收器将不需要在每个级别上手动转发,而只需传递到入口边缘:

ctx.a() -> b() -> c() -> d()

B、c 和 d 将被隐式调用,而无需您通过接收器显式调用每个级别函数,因为每个函数已经在该上下文中运行,因此它可以访问其属性(依赖项)自动启用。

因此,一旦我们了解了两者,我们就需要选择一种或任何其他 DI 方法。这是相当主观的,因为在函数世界中还有其他注入依赖项的替代方法,例如依赖类型类及其编译时间分辨率的无标记最终方法,或者在 Arrow 中仍然不可用但很快就会出现的 EnvIO(或等效替代)。但我不想在这里让你更加困惑。在我看来,Reader 与 IO 等其他常见数据类型结合起来有点“嘈杂”,我通常瞄准无标记的最终方法,因为这些方法允许保持由注入的类型类确定的程序约束并完全依赖 IO 运行时你的程序。

希望这对您有所帮助,否则请随时再次提问,我们会回来回答

于 2020-02-23T21:03:18.637 回答