Scala中的链接验证
我有一个ScalaScala中的链接验证,scala,monads,either,Scala,Monads,Either,我有一个Scalacase类,包含命令行配置信息: case class Config(emailAddress: Option[String], firstName: Option[String] lastName: Option[String] password: Option[String]) 我正在编写一个验证函数,用于检查每个值是否为某个Some: def validateCo
case类
,包含命令行配置信息:
case class Config(emailAddress: Option[String],
firstName: Option[String]
lastName: Option[String]
password: Option[String])
我正在编写一个验证函数,用于检查每个值是否为某个Some
:
def validateConfig(config: Config): Try[Config] = {
if (config.emailAddress.isEmpty) {
Failure(new IllegalArgumentException("Email Address")
} else if (config.firstName.isEmpty) {
Failure(new IllegalArgumentException("First Name")
} else if (config.lastName.isEmpty) {
Failure(new IllegalArgumentException("Last Name")
} else if (config.password.isEmpty) {
Failure(new IllegalArgumentException("Password")
} else {
Success(config)
}
}
但是,如果我理解Haskell的Monad,那么我似乎应该能够将验证链接在一起(伪语法):
如果任何config.XXX
表达式返回Failure
,整个(validateConfig
)应该失败,否则应该返回Success(config)
有什么方法可以用
Try
,或者其他类来实现吗?这是一个case类,为什么不用模式匹配来实现呢
def validateConfig(config: Config): Try[Config] = config match {
case Config(None, _, _, _) => Failure(new IllegalArgumentException("Email Address")
case Config(_, None, _, _) => Failure(new IllegalArgumentException("First Name")
case Config(_, _, None, _) => Failure(new IllegalArgumentException("Last Name")
case Config(_, _, _, None) => Failure(new IllegalArgumentException("Password")
case _ => Success(config)
}
在你的简单例子中,我的首要任务是忘记单子和链锁,只要去掉那种讨厌的if…else
味道就行了
然而,虽然case类对于短列表非常有效,但是对于大量配置选项,这会变得很乏味,并且错误的风险也会增加。在这种情况下,我会考虑这样的事情:
def optionMap: OptionMap = ...
def validate: Either[List[String], OptionMap] = {
val badOptions = optionMap collect { case (s, None) => s }
if (badOptions.size > 0)
Left(badOptions)
else
Right(optionMap)
}
None
type OptionMap = scala.collection.immutable.Map[String, Option[Any]]
而Config
类有如下方法:
def optionMap: OptionMap = ...
def validate: Either[List[String], OptionMap] = {
val badOptions = optionMap collect { case (s, None) => s }
if (badOptions.size > 0)
Left(badOptions)
else
Right(optionMap)
}
然后我会这样写Config.validate
:
def optionMap: OptionMap = ...
def validate: Either[List[String], OptionMap] = {
val badOptions = optionMap collect { case (s, None) => s }
if (badOptions.size > 0)
Left(badOptions)
else
Right(optionMap)
}
因此,现在Config.validate
返回一个Left
包含所有坏选项的名称,或者返回一个Right
包含选项及其值的完整映射。坦率地说,您在右侧的中输入的内容可能并不重要
现在,任何想要验证Config
的东西都只需调用Config.validate
并检查结果。如果它是一个左
,它可以抛出一个IllegalArgumentException
,其中包含一个或多个坏选项的名称。如果它是正确的
,它可以做它想做的任何事情,知道配置
是有效的
因此,我们可以将您的validateConfig
函数重写为
def validateConfig(config: Config): Try[Config] = config.validate match {
case Left(l) => Failure(new IllegalArgumentException(l.toString))
case _ => Success(config)
}
你能看到它的功能和面向对象性有多强吗
- 没有命令链
if…else
Config
对象验证自身
Config
对象无效的后果留给较大的程序处理
不过,我认为一个真实的例子会更加复杂。您通过说“它是否包含Option[String]
或None
?”来验证选项,但不检查字符串本身的有效性。实际上,我认为您的Config
类应该包含一个选项映射,其中名称映射到值和验证字符串的匿名函数。我可以描述如何扩展上述逻辑以使用该模型,但我想我将把它留给您作为练习。我将给您一个提示:您可能不仅要返回失败选项的列表,还要返回每种情况下失败的原因
哦,顺便说一下。。。我希望以上任何一点都不意味着我认为您应该将选项及其值作为optionMap
存储在对象内部。我认为这样检索它们是有用的,但我永远不会鼓励这样暴露实际的内部表示;) 这是一个case类,那么为什么不使用模式匹配呢
def validateConfig(config: Config): Try[Config] = config match {
case Config(None, _, _, _) => Failure(new IllegalArgumentException("Email Address")
case Config(_, None, _, _) => Failure(new IllegalArgumentException("First Name")
case Config(_, _, None, _) => Failure(new IllegalArgumentException("Last Name")
case Config(_, _, _, None) => Failure(new IllegalArgumentException("Password")
case _ => Success(config)
}
在你的简单例子中,我的首要任务是忘记单子和链锁,只要去掉那种讨厌的if…else
味道就行了
然而,虽然case类对于短列表非常有效,但是对于大量配置选项,这会变得很乏味,并且错误的风险也会增加。在这种情况下,我会考虑这样的事情:
def optionMap: OptionMap = ...
def validate: Either[List[String], OptionMap] = {
val badOptions = optionMap collect { case (s, None) => s }
if (badOptions.size > 0)
Left(badOptions)
else
Right(optionMap)
}
添加一个方法,使用选项名作为键,返回配置选项的键->值映射
让验证方法检查映射中是否有任何值为None
如果没有这样的值,则返回success
如果至少有一个值匹配,则返回该值名称并返回错误
所以假设某处是有定义的
type OptionMap = scala.collection.immutable.Map[String, Option[Any]]
而Config
类有如下方法:
def optionMap: OptionMap = ...
def validate: Either[List[String], OptionMap] = {
val badOptions = optionMap collect { case (s, None) => s }
if (badOptions.size > 0)
Left(badOptions)
else
Right(optionMap)
}
然后我会这样写Config.validate
:
def optionMap: OptionMap = ...
def validate: Either[List[String], OptionMap] = {
val badOptions = optionMap collect { case (s, None) => s }
if (badOptions.size > 0)
Left(badOptions)
else
Right(optionMap)
}
因此,现在Config.validate
返回一个Left
包含所有坏选项的名称,或者返回一个Right
包含选项及其值的完整映射。坦率地说,您在右侧的中输入的内容可能并不重要
现在,任何想要验证Config
的东西都只需调用Config.validate
并检查结果。如果它是一个左
,它可以抛出一个IllegalArgumentException
,其中包含一个或多个坏选项的名称。如果它是正确的
,它可以做它想做的任何事情,知道配置
是有效的
因此,我们可以将您的validateConfig
函数重写为
def validateConfig(config: Config): Try[Config] = config.validate match {
case Left(l) => Failure(new IllegalArgumentException(l.toString))
case _ => Success(config)
}
你能看到它的功能和面向对象性有多强吗
- 没有命令链
if…else
Config
对象验证自身
Config
对象无效的后果留给较大的程序处理
不过,我认为一个真实的例子会更加复杂。您通过说“它是否包含Option[String]
或None
?”来验证选项,但不检查字符串本身的有效性。实际上,我认为您的Config
类应该包含一个选项映射,其中名称映射到值和验证字符串的匿名函数。我可以描述如何扩展上述逻辑以使用该模式