将 HList 传递给函数(使用隐式 LeftFolder)
Passing an HList to a function (with an implicit LeftFolder)
我正在尝试编写一个允许通过 HList 链接规则的小型 Rule/Validation 引擎。现在下面的代码可以正常编译,但我无法使用 Validation
class 作为初学者,我被困在这里找出原因。
trait Rule {
type Value
}
object Rule {
type Aux[T] = Rule { type Value = T }
}
abstract class RuleOps[R <: Rule]( rule: R )(
implicit
definition: Definition[R],
show: Show[R]
) {
def validate( value: R#Value ): Result[R#Value] = {
definition( value, rule ) match {
case true ⇒ Success( value )
case false ⇒ Failure( value, Seq( show( value, rule ) ) )
}
}
}
case class Validation[T, H <: HList]( rules: H )(
implicit fold: LeftFolder.Aux[H, T, combine.type, Result[T]]
) {
def validate( value: T ): Result[T] = {
rules.foldLeft( value )( combine )
}
}
object Validation {
object combine extends Poly {
/**
* First fold element retrieves a value input and generates either a
* Success or a Failure
*/
implicit def head[R <: Rule]( implicit definition: Definition[R], show: Show[R] ) = {
use( ( value: R#Value, rule: R ) ⇒ rule.validate( value ) )
}
/**
* If the previous fold returned a Success, the next rule is validated
*/
implicit def success[R <: Rule]( implicit definition: Definition[R], show: Show[R] ) = {
use( ( rule: R, success: Success[R#Value] ) ⇒ {
head.apply( success.value, rule ) )
}
}
/**
* If the previous fold returned a Failure, all succeeding folds will
* return Failures as well
*
* When this case fails, the additional error messages are appended to
* the input Failure. In case of a successful validation, the input
* Failure is passed along.
*/
implicit def failure[R <: Rule]( implicit definition: Definition[R], show: Show[R] ) = {
use( ( rule: R, failure: Failure[R#Value] ) ⇒ {
rule.validate( failure.value ) match {
case Failure( _, messages ) ⇒
( lens[Failure[R#Value]] >> 'messages ).modify( failure )( _ ++ messages )
case Success( _ ) ⇒ failure
}
} )
}
}
}
使用 Validation
会产生隐式缺失错误
> Validation( Required[String]() :: Email() :: HNil ).validate( "asdf" )
> [error] could not find implicit value for parameter fold: LeftFolder.Aux[Required[String] :: Email :: HNil, T, combine.type, Result[T]]
> [error] Validation( Required[String]() :: Email() :: HNil ).validate( "asdf" )
> [error] ^
> [error] one error found
我怀疑 fold cases additional implicits parameters (defintion
& show
) 是造成这种情况的原因,但暂时删除它们对错误没有影响。
我设法整合了一个可行的解决方案。我认为我的错误是匹配 Failure
和 Success
,而不是 Result
。但这更像是一种直觉..
caseTail
中的运行时模式匹配有些麻烦,但作为概念证明我觉得没问题。
scala> :paste
// Entering paste mode (ctrl-D to finish)
import shapeless._
import shapeless.ops.hlist.LeftFolder
trait Rule {
type Value
}
object Rule {
type Aux[T] = Rule { type Value = T }
}
implicit class RuleOps[R <: Rule]( rule: R )(
implicit
definition: Definition[R],
show: Show[R]
) {
def validate( value: R#Value ): Result[R#Value] = {
definition( value, rule ) match {
case true ⇒ Success( value )
case false ⇒ Failure( value, Seq( show( value, rule ) ) )
}
}
}
/**
* Type class that defines the actual validation logic
*/
trait Definition[-R <: Rule] {
def apply( value: R#Value, rule: R ): Boolean
}
/**
* Type class that renders an error message for a failed rule validation
*/
trait Show[-R <: Rule] {
def apply( value: R#Value, rule: R ): String
}
/**
* A Result is the outcome of a rule(s) validation
*/
sealed trait Result[+T] { def value: T }
case class Success[+T]( value: T ) extends Result[T]
case class Failure[+T]( value: T, messages: Seq[String] ) extends Result[T]
/**
* Validate a value against a list of rules
*/
case class Validation[T, H <: HList]( rules: H )( implicit f: LeftFolder.Aux[H, T, combine.type, Result[T]] ) {
def validate( value: T ): Result[T] = rules.foldLeft( value )( combine )
}
object combine extends Poly2 {
implicit def caseHead[R <: Rule](
implicit
definition: Definition[R],
show: Show[R]
): Case.Aux[R#Value, R, Result[R#Value]] = {
at[R#Value, R]( ( value, rule ) ⇒ {
rule.validate( value )
} )
}
implicit def caseTail[T, R <: Rule.Aux[T]](
implicit
definition: Definition[R],
show: Show[R]
): Case.Aux[Result[T], R, Result[T]] = {
at[Result[T], R]( ( result, rule ) ⇒ {
rule.validate( result.value )
} )
}
}
trait Email extends Rule {
override type Value = String
}
object Email extends Email {
implicit val dfn = new Definition[Email] {
override def apply( value: String, rule: Email ) = false
}
implicit val show = new Show[Email] {
override def apply( value: String, rule: Email ) = "error.email"
}
}
// Exiting paste mode, now interpreting.
import shapeless._
import shapeless.ops.hlist.LeftFolder
defined trait Rule
defined object Rule
defined class RuleOps
defined trait Definition
defined trait Show
defined trait Result
defined class Success
defined class Failure
defined class Validation
defined object combine
defined trait Email
defined object Email
scala> Email.validate( "asdf" )
res0: Result[Email.Value] = Failure(asdf,List(error.email))
scala> Validation( Email :: HNil ).validate( "asdf" )
res1: Result[String] = Failure(asdf,List(error.email))
scala> Validation( Email :: Email :: HNil ).validate( "asdf" )
res2: Result[String] = Failure(asdf,List(error.email))
我正在尝试编写一个允许通过 HList 链接规则的小型 Rule/Validation 引擎。现在下面的代码可以正常编译,但我无法使用 Validation
class 作为初学者,我被困在这里找出原因。
trait Rule {
type Value
}
object Rule {
type Aux[T] = Rule { type Value = T }
}
abstract class RuleOps[R <: Rule]( rule: R )(
implicit
definition: Definition[R],
show: Show[R]
) {
def validate( value: R#Value ): Result[R#Value] = {
definition( value, rule ) match {
case true ⇒ Success( value )
case false ⇒ Failure( value, Seq( show( value, rule ) ) )
}
}
}
case class Validation[T, H <: HList]( rules: H )(
implicit fold: LeftFolder.Aux[H, T, combine.type, Result[T]]
) {
def validate( value: T ): Result[T] = {
rules.foldLeft( value )( combine )
}
}
object Validation {
object combine extends Poly {
/**
* First fold element retrieves a value input and generates either a
* Success or a Failure
*/
implicit def head[R <: Rule]( implicit definition: Definition[R], show: Show[R] ) = {
use( ( value: R#Value, rule: R ) ⇒ rule.validate( value ) )
}
/**
* If the previous fold returned a Success, the next rule is validated
*/
implicit def success[R <: Rule]( implicit definition: Definition[R], show: Show[R] ) = {
use( ( rule: R, success: Success[R#Value] ) ⇒ {
head.apply( success.value, rule ) )
}
}
/**
* If the previous fold returned a Failure, all succeeding folds will
* return Failures as well
*
* When this case fails, the additional error messages are appended to
* the input Failure. In case of a successful validation, the input
* Failure is passed along.
*/
implicit def failure[R <: Rule]( implicit definition: Definition[R], show: Show[R] ) = {
use( ( rule: R, failure: Failure[R#Value] ) ⇒ {
rule.validate( failure.value ) match {
case Failure( _, messages ) ⇒
( lens[Failure[R#Value]] >> 'messages ).modify( failure )( _ ++ messages )
case Success( _ ) ⇒ failure
}
} )
}
}
}
使用 Validation
会产生隐式缺失错误
> Validation( Required[String]() :: Email() :: HNil ).validate( "asdf" )
> [error] could not find implicit value for parameter fold: LeftFolder.Aux[Required[String] :: Email :: HNil, T, combine.type, Result[T]]
> [error] Validation( Required[String]() :: Email() :: HNil ).validate( "asdf" )
> [error] ^
> [error] one error found
我怀疑 fold cases additional implicits parameters (defintion
& show
) 是造成这种情况的原因,但暂时删除它们对错误没有影响。
我设法整合了一个可行的解决方案。我认为我的错误是匹配 Failure
和 Success
,而不是 Result
。但这更像是一种直觉..
caseTail
中的运行时模式匹配有些麻烦,但作为概念证明我觉得没问题。
scala> :paste
// Entering paste mode (ctrl-D to finish)
import shapeless._
import shapeless.ops.hlist.LeftFolder
trait Rule {
type Value
}
object Rule {
type Aux[T] = Rule { type Value = T }
}
implicit class RuleOps[R <: Rule]( rule: R )(
implicit
definition: Definition[R],
show: Show[R]
) {
def validate( value: R#Value ): Result[R#Value] = {
definition( value, rule ) match {
case true ⇒ Success( value )
case false ⇒ Failure( value, Seq( show( value, rule ) ) )
}
}
}
/**
* Type class that defines the actual validation logic
*/
trait Definition[-R <: Rule] {
def apply( value: R#Value, rule: R ): Boolean
}
/**
* Type class that renders an error message for a failed rule validation
*/
trait Show[-R <: Rule] {
def apply( value: R#Value, rule: R ): String
}
/**
* A Result is the outcome of a rule(s) validation
*/
sealed trait Result[+T] { def value: T }
case class Success[+T]( value: T ) extends Result[T]
case class Failure[+T]( value: T, messages: Seq[String] ) extends Result[T]
/**
* Validate a value against a list of rules
*/
case class Validation[T, H <: HList]( rules: H )( implicit f: LeftFolder.Aux[H, T, combine.type, Result[T]] ) {
def validate( value: T ): Result[T] = rules.foldLeft( value )( combine )
}
object combine extends Poly2 {
implicit def caseHead[R <: Rule](
implicit
definition: Definition[R],
show: Show[R]
): Case.Aux[R#Value, R, Result[R#Value]] = {
at[R#Value, R]( ( value, rule ) ⇒ {
rule.validate( value )
} )
}
implicit def caseTail[T, R <: Rule.Aux[T]](
implicit
definition: Definition[R],
show: Show[R]
): Case.Aux[Result[T], R, Result[T]] = {
at[Result[T], R]( ( result, rule ) ⇒ {
rule.validate( result.value )
} )
}
}
trait Email extends Rule {
override type Value = String
}
object Email extends Email {
implicit val dfn = new Definition[Email] {
override def apply( value: String, rule: Email ) = false
}
implicit val show = new Show[Email] {
override def apply( value: String, rule: Email ) = "error.email"
}
}
// Exiting paste mode, now interpreting.
import shapeless._
import shapeless.ops.hlist.LeftFolder
defined trait Rule
defined object Rule
defined class RuleOps
defined trait Definition
defined trait Show
defined trait Result
defined class Success
defined class Failure
defined class Validation
defined object combine
defined trait Email
defined object Email
scala> Email.validate( "asdf" )
res0: Result[Email.Value] = Failure(asdf,List(error.email))
scala> Validation( Email :: HNil ).validate( "asdf" )
res1: Result[String] = Failure(asdf,List(error.email))
scala> Validation( Email :: Email :: HNil ).validate( "asdf" )
res2: Result[String] = Failure(asdf,List(error.email))