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

我正在尝试编写一个小规则/验证引擎,允许通过HLists链接规则.现在下面的代码编译得很好,但是我无法使用Validation类,作为初学者,我被困在这里找出原因.

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( 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

我怀疑折叠情况附加的implicits参数(定义和显示)是导致这种情况的原因,但暂时删除它们对错误没有影响.

更新Working code example

最佳答案 我设法将一个有效的解决方案放在一起.我认为我的错误是与失败和成功相匹配,而不是结果.但这更像是一种直觉……

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))
点赞