Passing an HList to a function (with an implicit LeftFolder)

I am trying to write a small Rule / Validation mechanism that allows you to bind rules using HLists. Now the code below compiles fine, but I cannot use the class Validation, and as a beginner I am stuck here figuring out why.

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
                }
            } )
        }
    }
}

Use Validationleads to an implicit error

> 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

I suspected that the implicits ( defintionand show) additional parameters were the cause of the failure , but their temporary removal did not affect the error.

Refresh Working Code Example

+4
source share
1 answer

. , , 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))
+2

Source: https://habr.com/ru/post/1605783/


All Articles