您的疑问是可以理解的,因为是的,这两种方法都有相同的结果:在调用堆栈中一直为您隐式传递依赖关系,因此您不需要在每个级别都显式传递它们。使用这两种方法,您都会从外部边缘传递一次依赖项,仅此而已。
假设您有函数 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 代表需要一些依赖项才能运行的计算。 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 的帖子中描述的方法看起来不同,但最终实现了相同的效果。这种方法是关于利用 Kotlin 扩展函数,因为通过定义一个程序来处理所有级别的接收器类型(我们称之为上下文)将意味着每个级别都可以访问所提到的上下文及其属性。即:
Context.a()
Context.b()
Context.c()
Context.d()
请注意,扩展函数接收器是一个参数,如果没有扩展函数支持,您需要在每次调用时手动作为附加函数参数传递,因此这种方式是函数所需的依赖项或“上下文”跑步。通过这种方式理解这些并理解 Kotlin 如何解释扩展函数,接收器将不需要在每个级别上手动转发,而只需传递到入口边缘:
ctx.a() -> b() -> c() -> d()
B、c 和 d 将被隐式调用,而无需您通过接收器显式调用每个级别函数,因为每个函数已经在该上下文中运行,因此它可以访问其属性(依赖项)自动启用。
因此,一旦我们了解了两者,我们就需要选择一种,或任何其他 DI 方法。这是相当主观的,因为在函数世界中还有其他注入依赖项的替代方法,例如依赖类型类及其编译时间分辨率的无标记最终方法,或者在 Arrow 中仍然不可用但很快就会出现的 EnvIO(或等效替代)。但我不想在这里让你更加困惑。在我看来,Reader 与 IO 等其他常见数据类型结合起来有点“嘈杂”,我通常瞄准无标记的最终方法,因为这些方法允许保持由注入的类型类确定的程序约束并完全依赖 IO 运行时你的程序。
希望这对您有所帮助,否则请随时再次提问,我们会回来回答?