将HList传递给函数(使用隐式LeftFolder)

时间:2015-09-03 16:20:45

标签: scala fold shapeless

我正在尝试编写一个小规则/验证引擎,允许通过HLists链接规则。现在下面的代码编译得很好,但是我无法使用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 会产生隐式遗漏错误

defintion

我怀疑折叠案例的其他隐含参数(show&amp; {{1}})是导致这种情况的原因,但暂时删除它们对错误没有影响。

更新 Working code example

1 个答案:

答案 0 :(得分:2)

我设法将一个有效的解决方案放在一起。我认为我的错误是与FailureSuccess匹配,而不是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))