Arrow KT:Reader 依赖注入的 Monad 与 @extension

Arrow KT: Reader Monad vs @extension for Dependency Injection

我读过 Reader Monad from this article by Jorge Castillo himself and I've also got this article by Paco。似乎两者都以不同的方式处理 Dependency Injection 的想法。 (还是我错了?)

我真的很困惑我是否理解整个Reader Monad and how it relates to the Simple Depenency Injection that Paco在说什么。

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

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

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

如果你没有使用任何提到的机制,并且你需要 d() 中的一些依赖项,你将最终转发你的依赖项(让我们称它们为 ctx)在每个级别上一直向下:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

因此,一旦我们理解了两者,我们就需要选择一种或任何其他 DI 方法。这是相当主观的,因为在功能世界中还有其他注入依赖项的替代方法,例如依赖类型 类 及其编译时间分辨率的无标记最终方法,或者 Arrow 中仍然不可用但将会很快(或等效的替代方案)。但我不想在这里让你更加困惑。在我看来,Reader 有点 "noisy" 与其他常见数据类型(如 IO)结合使用,我通常以无标记最终方法为目标,因为这些方法允许保留由注入类型 [=53] 确定的程序约束=] 并依靠 IO 运行时间来完成你的程序。

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