在Scala中免费验证
优锐课带你详细了解如何在Scala中实施免费的monad验证。抽丝剥茧,细说架构那些事!
由于业务数据的复杂性,已经在数据验证上花费了很多精力。在Scala中,提出了使用应用程序进行验证的方法,并被广泛认为是一种有效的方法。受应用验证和免费monad的思想启发,在本文中,我们介绍了一个monadic验证框架,该框架“免费”构建验证。我们将进一步讨论此方法,并通过示例代码演示实现。
在Scala中验证
当检测到错误时,可以以不同的形式找到验证。遇到第一个错误(或异常)时,验证可以立即返回;验证结果可能包含也可能不包含验证错误或异常消息。这种情况称为快速失败验证,其中该验证未验证所有业务规则,并且仅返回零或一个消息,并且在出现第一个错误时应缩短该过程。这种简单的验证形式有时被认为是不充分的,因为没有累积的错误就无法进行完整的验证。实际上,它广泛用于应用程序开发中。 Scala中的Monadic实体(例如Option,Try和Either)非常易于使用。
验证所有业务规则和累积错误与快速失败的验证有很大不同。提出了应用函子,它们有效地解决了累积问题。流行的Scala库,例如scalaz和Cats,都提供了应用程序验证支持。读者可以参考scalaz.Validation API和cats.data.Validated API了解更多详细信息。
但是,由于各种原因,用Scala编写的许多项目都不使用这两个库中的任何一个,并且Scala语言没有对应用性理解的本地支持。因此,一种不依赖任何第三方库的本地验证方法非常有吸引力。
验证问题与免费monad可以解决的问题非常相似——将一系列验证器建模为用于理解的工作流,并在解释器中执行它们。免费的monad提供了一种强大的方法,可以将验证程序隐式地提升到monad中(免费)。因此,我们需要解决的问题是对我们的解释器进行重新建模以在遇到错误或异常时继续执行。解释器应有能力保证每个验证器的执行,并且所有验证消息都应返回到呼叫站点。
验证器和免费Monad
在我们的方法中,验证是验证器执行的单子组合;每个验证器代表对每个业务规则的验证,由理解组成。这是免费的monad可以提供帮助的地方——可以将验证器隐式提升为“免费”的monad。这使我们的验证可以享受免费的monad可以提供的所有好处——堆栈免费和自然转换。
首先,我们讨论快速失败的验证框架及其局限性。
假设我们需要先验证一个人的姓名和年龄,然后再将该人保存到数据库中:
case class Person(name: String, age: Int){
def validateName= if (name.isEmpty) None else Some("Success")
def validateAge = if (age < 18) None else Some("Success")
}
def save(person: Person): Boolean = {
println(s"save ${person.name} at age ${person.age}")
true
}
val person = Person("Michael", 20)
for {
_ <- person.validateName
_ <- person.validateAge
} yield save(person)
上面的编码有两个限制:
1.验证不会将验证错误带到呼叫站点,遇到错误时将返回无
2.验证将在无时停止。为了理解,将上面的代码合并成一个flatMap链,然后是一个map:
person.validateName
.flatMap((_: String) =>person.validateAge.map((_: String) => save(person)))
如果名称为空,validateName返回None,该过程将短暂停止,并且validateAge将不会执行——这是快速失败的验证。
在我们的方法中,我们对验证器进行建模以使其符合简单特征:
sealed trait Validator[A] {
def validate: Option[Error]
def unbox: A
}
每个验证器都实现自己的验证规则:
case class NameValidator(name: String) extends Validator[String] {
def validate = if (name.isEmpty) Option(NameError) else None
def unbox: String = name
}
case class AgeValidator(age: Int) extends Validator[Int] {
def validate = if (age >= 18) None else Some(AgeError)
def unbox: Int = age
}
请注意此实现中的以下内容:
- 验证器是要验证的数据及其验证规则的容器。提供了“取消装箱”功能,以允许从验证器获取数据,这在稍后将讨论的实现中很重要;
- 我们的验证器不会在错误时返回None,而在错误时返回Some [Error],而没有错误则返回None。这使我们能够将错误消息传送回呼叫站点。
验证程序隐式提升为monad,就像在自由monad中一样:
implicit def liftF[F[_], A](fa: F[A]): Free[F, A] = FlatMap(fa, Return.apply)
免费的monad是以下形式的标准:
sealed trait Free[F[_], A] {
def flatMap[B](f: A => Free[F, B]): Free[F, B] = this match {
case Return(a) => f(a)
case FlatMap(sub, cont) => FlatMap(sub, cont andThen (_ flatMap f))
}
def map[B](f: A => B): Free[F, B] = flatMap(a => Return(f(a)))
}
final case class Return[F[_], A](a: A) extends Free[F, A]
case class FlatMap[F[_], I, A](sub: F[I], cont: I => Free[F, A]) extends Free[F, A]
解释器相应地执行验证器:
val interpreter = new Executor[Validator] {
override def exec[A](fa: Validator[A]) = fa.validate
override def unbox[A](fa: Validator[A]) = fa.unbox
}
def validate[F[_], A](prg: Free[F, A], interpreter: Executor[F]): List[Error] = {
def go(errorList: List[Option[Error]], prg: Free[F, A]): List[Option[Error]]=
prg match {
case Return(a) => errorList
case FlatMap(sub, cont) => go(interpreter.exec(sub) :: errorList,
cont(interpreter.unbox(sub)))
}
go(List.empty[Option[Error]], prg).flatten
}
解释器是数据,错误,数据验证器和自由单子之间的粘合剂。请注意,此解释器与免费monad中的解释器之间的三个重要的详细区别是:
- 解释器提供拆箱功能;它用于“no-error”情况。返回None类型时,unbox用于以类型安全的方式查找正在验证的数据。为了继续验证过程,反装箱又使用验证器提供的拆箱功能来获取正在验证的数据。
- 与免费monad中的解释器不同,该过程的继续是通过monadic操作完成的,可能会缩短为None:
executor.exec(sub).flatMap(x => validateAndRun(cont(x), executor))
在我们的验证解释器中,通过遵循验证器直到执行最后一个验证器来保证过程的继续,因为顺序执行是从flatMap
中取出的,但是仍然保持了尾递归位置,因此验证过程是堆栈——自由。
- 返回一个列表,其中包含来自每个验证器的验证错误消息(如果存在)。
验证组成
正如工作流是在免费monad中建模一样,验证流程也是通过理解建模的。例如,如果名称验证和年龄验证全部通过,我们将调用save(person)。否则,我们将打印出累积的错误:
val validation = for {
_ <- NameValidator(person.name)
_ <- AgeValidator(person.age)
} yield ()
validate(validation, interpreter) match {
case Nil => save(person)
case errors => errors foreach println
}
可以在免费验证中找到该实现。
结论
免费的monad是一种允许你从任何Functor构建monad的构造。 像其他单子一样,它是表示和操纵计算的一种纯方法。
特别是免费的monad提供了一种实用的方法:
- 将状态计算表示为数据,然后运行它们
- 以堆栈安全的方式运行递归计算
- 构建嵌入式DSL(特定于域的语言)
- 使用自然转换将计算重新定位到另一个解释器
(以上是项目类型的重点)
根据Leif Battermann的说法:
“应用程序使我们能够编写独立的操作并评估每个操作。即使中间评估失败。这也使我们能够收集错误消息,而不仅仅是返回发生的第一个错误。”
在本文中,我们介绍了一种为你提供两全其美的方法——一种验证框架,它是不带应用程序的免费monad。希望你喜欢这个简短的演示!
另外近期整理了一套完整的java架构思维导图,分享给同样正在认真学习的每位朋友~
在Scala中免费验证的更多相关文章
- Scala中Iterator允许执行一次
背景 使用spark执行mapPartitionsWithIndex((index,iterator)=>{....}),在执行体中将iterator进行一次迭代后,再次根据iterator执行 ...
- Scala 中 构造函数,重载函数的执行顺序
在调试scala在线开发教程(http://www.imobilebbs.com/wordpress/archives/4911)的过程中看到了以下代码,但是这段代码无论怎么调试都无法成功. abst ...
- C# 中参数验证方式的演变
一般在写方法的时候,第一步就是进行参数验证,这也体现了编码者的细心和缜密,但是在很多时候这个过程很枯燥和乏味,比如在拿到一个API设计文档的时候,通常会规定类型参数是否允许为空,如果是字符可能有长度限 ...
- Scala中apply的用法
Scala中的 apply 方法有着不同的含义, 对于函数来说该方法意味着调用function本身, 以下说明摘自Programming in Scala, 3rd Edition Every fun ...
- MVVM中数据验证之 ViewModel vs. Model
MMVM模式示意图. View绑定到ViewModel,然后执行一些命令在向它请求一个动作.而反过来 ...
- [Asp.net MVC]Asp.net MVC5系列——在模型中添加验证规则
目录 概述 在模型中添加验证规则 自定义验证规则 伙伴类的使用 总结 系列文章 [Asp.net MVC]Asp.net MVC5系列——第一个项目 [Asp.net MVC]Asp.net MVC5 ...
- scala中的面向对象定义类,构造函数,继承
我们知道scala中一切皆为对象,函数也是对象,数字也是对象,它是一个比java还要面向对象的语言. 定义scala的简单类 class Point (val x:Int, val y:Int) 上面 ...
- Scala中的None,Nothing,Null,Nil
在scala中这四个类型名称很类似,作用确实完全不同的. None是一个object,是Option的子类型,定义如下 case object None extends Option[Nothing] ...
- 第52讲:Scala中路径依赖代码实战详解
今天学习了scala中的路径依赖,来看一下实战代码 class Outer{ private val x = 10 class Inner{ private val y = x +10 } ...
随机推荐
- 【GET TIPS】Chrome所见即所得的截图技巧
精简的前言: 对,我就是想说下事情的来龙去脉.您要不想听,就把耳朵捂起来23333. 想截个新冠肺炎病毒,全国确诊人数今日增长的图,以确定非湖北地区不再明显增长. 但由于网页需要的内容分布在两页,需要 ...
- WinFrom 在Devexpress里用GridControl和DataNavigtor进行分页
1,分页嘛先要有个SQL 程序才能写下去 先提供下SQL的思路,对于分页的SQL我之前帖子有介绍,就不一一介绍了 select top pageSize * --显示数量 from (select r ...
- SAP S4HANA里委外加工采购功能的变化
SAP S4HANA里委外加工采购功能的变化 [Part 1:主要变化点] 1.1,采购订单界面上的变化, 1.2, 新的事务代码: ME2ON (Subcontracting Cockpit), 1 ...
- 7.Android-压力测试、单元测试、日志猫使用
1.压力测试monkey 通过cmd输入下面命令: adb shell monkey -p com.example.phonecall --ignore-crashes --ignore-timeou ...
- 「Flink」事件时间与水印
我们先来以滚动时间窗口为例,来看一下窗口的几个时间参数与Flink流处理系统时间特性的关系. 获取窗口开始时间Flink源代码 获取窗口的开始时间为以下代码: org.apache.flink.str ...
- ORACLE ANALYZE使用小结
ANALYZE的介绍 使用ANALYZE可以收集或删除对象的统计信息.验证对象的结构.标识表或cluster中的行迁移/行链接信息等.官方文档关于ANALYZE功能介绍如下: · ...
- nginx 修改上传文件的大小限制
nginx默认的上传文件大小是有限制的,一般为2MB,如果你要上传的文件超出了这个值,将可能上传失败.修改的地方有: 1. php.ini: upload_max_filesize = 8M 2. ...
- Git的基本使用 -- 文件的添加、撤销、对比、删除
显示当前工作区.暂存区.仓库的状态 git status 当工作区的所有文件都提交到仓库,并和仓库保持一致时 有修改的文件时,会显示有改动的文件,并提示如何提交这些修改 添加到暂存区,还未提交到仓库时 ...
- 调整markdown css样式
H1标题 H2标题 H3标题 H4标题 H5标题 H6标题 段落: 世情薄,人情恶.雨送黄昏花易落.晓风干,泪痕残.欲笺心事,独语斜阑.难,难,难! 人成各,今非昨.病魂常似秋千索.角声寒,夜阑珊.怕 ...
- A tiny problem with integers
# A tiny problem with integers 给定长度为N的数列A,然后输入M行操作指令. 第一类指令形如“C l r d”,表示把数列中第l~r个数都加d. 第二类指令形如“Q X” ...