Scala的配置数据 - 我应该使用Reader单子?(Configuration data in

2019-07-30 04:12发布

如何创建Scala中的一个正常功能配置的对象? 我曾经看过托尼·莫里斯在视频Reader单子,我仍然无法连接点。

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

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

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

我想Client.clients要在运行时确定,与无论是从性能看完文件的灵活性,或从数据库。 在Java世界我会定义一个接口,实现两种电源,并用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,它似乎并没有对我真的很麻烦,因为我知道这是注射一次-和-只有一次)。

将在什么Reader单子看起来像在这种情况下,并解释给我,就像我在5,什么是它的优势是什么?

Answer 1:

让我们先从你的方法和之间的简单,肤浅的差异Reader的方法,这是因为你不再需要挂到config在任何地方都没有。 比方说,你定义下面隐约聪明型同义词:

type Configured[A] = ConfigSource => A

现在,如果我需要一个ConfigSource一些功能,说得到第N”客户端列表中的一个功能,我可以声明功能为‘配置’:

def nthClient(n: Int): Configured[Client] = {
  config => config.clients(n)
}

因此,我们基本上是拉着一个config凭空,任何时候我们就需要一个! 闻起来像依赖注入,对不对? 现在,让我们说,我们要在第一,第二和第三的客户年龄在列表中(假设他们存在):

def ages: Configured[(Int, Int, Int)] =
  for {
    a0 <- nthClient(0)
    a1 <- nthClient(1)
    a2 <- nthClient(2)
  } yield (a0.age, a1.age, a2.age)

对于这一点,当然,你需要一些适当的定义mapflatMap 。 我不会进入这里,但会简单地说,Scalaz(或Rúnar的真棒NEScala谈话 ,或托尼的 ,你已经看到)给你所有你需要的。

这里最重要的一点是, ConfigSource依赖和其所谓的注射大多是隐藏的。 唯一的“暗示”,我们可以在这里看到的是, ages是一个类型的Configured[(Int, Int, Int)]而不是简单的(Int, Int, Int) 我们并不需要显式引用config的任何地方。

顺便说一句 ,这是我几乎总是喜欢去想单子的方式:他们隐藏自己的作用 ,所以它不会污染你的代码的流动,同时明确声明的类型签名的效果 。 换句话说,你不必重复自己太多:你说的函数的返回类型“哎,这个功能与作用X交易”,也不要惹它的任何进一步。

在这个例子中,当然效果是从一些固定环境阅读。 你可能熟悉另一单子效果包括错误处理:我们可以说, Option隐藏的错误处理逻辑,同时使你的方法的类型明确错误的可能性。 或者,那种阅读相反的,在Writer单子隐藏我们谨此而它的存在在类型系统明确的东西。

现在终于,就像我们通常需要启动一个DI框架(外我们通常控制流的地方,如在一个XML文件),我们还需要引导这个好奇的单子。 当然,我们还会有一些合理的切入点,以我们的代码,如:

def run: Configured[Unit] = // ...

它最终被相当简单:因为Configured[A]仅仅是功能的类型同义词ConfigSource => A ,我们就可以应用功能,它的“环境”:

run(ConfigFileSource)
// or
run(DatabaseSource)

当当! 因此,与传统的Java风格DI方法对比,我们没有任何“神奇”发生在这里。 唯一的法宝,因为它是被封装在我们的定义Configured类型,它表现为一个单子的方式。 最重要的是, 该类型的系统让我们诚实面对这“境界”依赖注入在发生:与任何类型Configured[...]在DI世界,没有任何东西是没有的。 我们根本就没有在老学校DI,这里的一切都可能通过魔法管理得到这个,所以你真的不知道你的代码的某些部分是安全的DI框架之外再利用(例如,您的单位内测试中,或在其他一些项目完全)。


更新:我写了一个博客张贴这也解释了Reader更详细。



文章来源: Configuration data in Scala — should I use the Reader monad?