如何使用Scala Cats验证正确的方法?

sas*_*has 11 validation scala functor scala-cats

以下是我的用例

  1. 我正在使用Cats来验证我的配置.我的配置文件在json中.
  2. Config使用lift-json将配置文件反序列化到我的case类,然后使用Cats验证它.我用这个作为指导.
  3. 我使用Cats的动机是收集验证时出现的所有错误.

我的问题是指南中给出的例子,属于类型

case class Person(name: String, age: Int)

def validatePerson(name: String, age: Int): ValidationResult[Person] = {
   (validateName(name),validate(age)).mapN(Person)
}
Run Code Online (Sandbox Code Playgroud)

但在我的情况下,我已经将我的配置反序列化为我的case类(下面是一个示例),然后我将它传递给验证

case class Config(source: List[String], dest: List[String], extra: List[String])

def vaildateConfig(config: Config): ValidationResult[Config] = {
  (validateSource(config.source), validateDestination(config.dest))
   .mapN { case _ => config }
}
Run Code Online (Sandbox Code Playgroud)

这里的区别是mapN { case _ => config }.因为我已经有一个配置,如果一切都有效,我不想从其成员重新创建配置.这是因为我传递配置以验证功能而不是它的成员.

我工作场所的一个人告诉我这不是正确的方法,因为Cats Validated提供了一种构造对象的方法,如果它的成员有效.如果对象的成员无效,则该对象不应存在或不应该是可构造的.这对我来说完全合情合理.

我应该做出任何改变吗?以上我在接受吗?

PS:上面的Config只是一个例子,我的真实配置可以有其他案例类作为其成员,它们本身可以依赖于其他案例类.

Tra*_*own 18

像Cats这样的图书馆推广的那种编程的核心目标之一就是使无效状态无法代表.在一个完美的世界中,根据这种理念,创建一个Config包含无效成员数据的实例是不可能的(通过使用像Refined这样的库,其中复杂的约束可以在类型系统中表达和跟踪,或者简单地通过隐藏不安全的构造函数).在一个稍微不完美的世界中,仍然可能构造无效的实例Config,但是不鼓励,例如通过使用安全构造函数(比如你的validatePerson方法Person).

听起来你处于一个更不完美的世界,你Config可能有或没有包含无效数据的实例,并且你想要验证它们以获得Config你知道有效的"新"实例.这是完全可能的,在某些情况下是合理的validateConfig,如果你陷入这个不完美的世界,你的方法是解决这个问题的完全合法的方法.

但缺点是,编译器无法跟踪已经验证的Config实例与尚未验证的实例之间的差异.你的程序中会有一些Config实例,如果你想知道它们是否已经过验证,你将需要追踪它们可能来自的所有地方.在某些情况下,这可能会很好,但对于大型或复杂的程序,它并不理想.

总结一下:理想Config情况下,无论何时创建实例(甚至可能无法创建无效实例),都可以验证实例,这样您就不必记住任何给定的Config是否好 - 类型系统可以为您记住.如果这是不可能的,因为例如您无法控制的API或定义,或者对于简单的用例来说它似乎太麻烦,您所做的事情validateConfig是完全合理的.


作为一个脚注,因为你在上面说过你有兴趣在Refined中查看更多细节,它在这种情况下为你提供的是一种避免形状更多功能的方法A => ValidationResult[A].现在你的validateName方法,例如,可能需要一个String并返回一个ValidationResult[String].你可以根据我对Config => ValidationResult[Config]上面的签名制作完全相同的参数- 一旦你使用结果(通过将函数映射到Validated或者其他),你只需要一个字符串,并且类型不会告诉你它已经过验证.

Refined允许你做的是写一个这样的方法:

def validateName(in: String): ValidationResult[Refined[String, SomeProperty]] = ...
Run Code Online (Sandbox Code Playgroud)

...其中SomeProperty可能指定了最小长度,或者字符串与特定正则表达式匹配的事实等等.重要的一点是,您没有验证String并返回String只有知道某事的情况 - 您正在验证String并返回一String,该编译器有所了解(通过Refined[A, Prop]包装).

再一次,这可能(好吧,可能是)对你的用例来说太过分了 - 你可能会发现很高兴知道你可以通过你的程序推动这个原则(在类型中跟踪验证).

  • @AndrzejSołtysik同意,虽然我认为这样的类型扩散有一个缺点.如果由于某种原因你需要打包输入并在一段时间内将它们带到未经验证的状态,但是,是的,一个新的"UnvalidatedConfig"是完全合理的. (4认同)
  • 非常感谢.确实是一个精心回答.得学新东西.希望可以给予更多的赞成. (3认同)
  • 我认为'newtypes'对配置用例非常有用.将配置拆分为两个独立的案例类,如`UnvalidatedConfig`和`Config`.您还可以隐藏`Config`的构造函数,以确保它的每个实例都必须由`validateConfig`方法创建. (2认同)