gpt4 book ai didi

scala - Scala 中的配置数据——我应该使用 Reader monad 吗?

转载 作者:行者123 更新时间:2023-12-03 08:39:45 25 4
gpt4 key购买 nike

如何在 Scala 中创建功能正确的可配置对象?我在 Reader 上观看了 Tony Morris 的视频。 monad,我仍然无法连接这些点。

我有一个 Client 的硬编码列表对象:

class Client(name : String, age : Int){ /* etc */}

object Client{
//Horrible!
val clients = List(Client("Bob", 20), Client("Cindy", 30))
}

我要 Client.clients在运行时确定,可以灵活地从属性文件或数据库中读取。在 Java 世界中,我会定义一个接口(interface),实现两种类型的源,并使用 DI 分配一个类变量:
trait ConfigSource { 
def clients : List[Client]
}

object ConfigFileSource extends ConfigSource {
override def clients = buildClientsFromProperties(Properties("clients.properties"))
//...etc, read properties files
}

object DatabaseSource extends ConfigSource { /* etc */ }

object Client {
@Resource("configuration_source")
private var config : ConfigSource = _ //Inject it at runtime

val clients = config.clients
}

这对我来说似乎是一个非常干净的解决方案(代码不多,意图明确),但是 var确实会跳出来(OTOH,在我看来这并不麻烦,因为我知道它只会被注入(inject)一次)。
Reader 会怎样? monad 在这种情况下看起来像,像我 5 岁一样向我解释,它有什么优势?

最佳答案

让我们从您的方法与 Reader 之间的一个简单的、表面的区别开始。方法,即您不再需要卡在config上任何地方。假设您定义了以下模糊巧妙的类型同义词:

type Configured[A] = ConfigSource => A

现在,如果我需要 ConfigSource对于某些函数,比如说一个获取列表中第 n 个客户端的函数,我可以将该函数声明为“已配置”:
def nthClient(n: Int): Configured[Client] = {
config => config.clients(n)
}

所以我们实际上是在拉一个 config凭空而来,我们随时需要!闻起来像依赖注入(inject),对吧?现在假设我们想要列表中第一个、第二个和第三个客户的年龄(假设它们存在):
def ages: Configured[(Int, Int, Int)] =
for {
a0 <- nthClient(0)
a1 <- nthClient(1)
a2 <- nthClient(2)
} yield (a0.age, a1.age, a2.age)

为此,当然,您需要对 map 进行适当的定义。和 flatMap .我不会在这里讨论这个,只是简单地说Scalaz(或 Rúnar's awesome NEScala talk,或你已经看到的 Tony's)给你所有你需要的东西。

这里重要的一点是 ConfigSource依赖及其所谓的注入(inject)大多是隐藏的。我们在这里可以看到的唯一“提示”是 agesConfigured[(Int, Int, Int)] 类型而不是简单的 (Int, Int, Int) .我们不需要显式引用 config任何地方。

As an aside, this is the way I almost always like to think about monads: they hide their effect so it's not polluting the flow of your code, while explicitly declaring the effect in the type signature. In other words, you needn't repeat yourself too much: you say "hey, this function deals with effect X" in the function's return type, and don't mess with it any further.

In this example, of course the effect is to read from some fixed environment. Another monadic effect you might be familiar with include error-handling: we can say that Option hides error-handling logic while making the possibility of errors explicit in your method's type. Or, sort of the opposite of reading, the Writer monad hides the thing we're writing to while making its presence explicit in the type system.



最后,正如我们通常需要引导一个 DI 框架(在我们通常的控制流之外的某个地方,例如在 XML 文件中),我们也需要引导这个奇怪的 monad。当然,我们的代码会有一些逻辑入口点,例如:
def run: Configured[Unit] = // ...

它最终变得非常简单:因为 Configured[A]只是函数 ConfigSource => A 的类型同义词,我们可以将函数应用到它的“环境”:
run(ConfigFileSource)
// or
run(DatabaseSource)

达达!因此,与传统的 Java 风格的 DI 方法相比,这里没有任何“魔法”发生。可以说,唯一的魔法封装在我们的 Configured 的定义中。类型以及它作为 monad 的行为方式。最重要的是, 类型系统让我们保持诚实 关于哪个“领域”依赖注入(inject)发生在:任何类型为 Configured[...]的东西存在于 DI 世界中,没有它的任何东西都不是。在老式的 DI 中我们根本无法做到这一点,在这种情况下,一切都可能由魔法管理,因此您并不真正知道代码的哪些部分可以安全地在 DI 框架之外重用(例如,在您的单元内)测试,或完全在其他项目中)。

更新:我写了一个 blog post这解释了 Reader更详细。

关于scala - Scala 中的配置数据——我应该使用 Reader monad 吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11253653/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com