我最近询问了 map 和 reduce/fold over scalaz.Validation 的 HList并得到了一个很好的答案,关于如何将固定大小的元组Va[T]
(它是 的别名scalaz.Validation[String, T]
)转换为scalaz.ValidationNel[String, T]
. 从那时起,我一直在研究 Shapeless 和类型级编程,以尝试提出一种适用于任何大小的元组的解决方案。
这就是我要开始的:
import scalaz._, Scalaz._, shapeless._, contrib.scalaz._, syntax.std.tuple._
type Va[A] = Validation[String, A]
// only works on pairs of Va[_]
def validate[Ret, In1, In2](params: (Va[In1], Va[In2]))(fn: (In1, In2) => Ret) = {
object toValidationNel extends Poly1 {
implicit def apply[T] = at[Va[T]](_.toValidationNel)
}
traverse(params.productElements)(toValidationNel).map(_.tupled).map(fn.tupled)
}
那么validate
我这样称呼的助手是:
val params = (
postal |> nonEmpty[String]("no postal"),
country |> nonEmpty[String]("no country") >=> isIso2Country("invalid country")
)
validate(params) { (postal, country) => ... }
我开始使用 anyProduct
而不是一对并将其内容限制为Va[T]
:
// needs to work with a tuple of Va[_] of arbitrary size
def validateGen[P <: Product, F, L <: HList, R](params: P)(block: F)(
implicit
gen: Generic.Aux[P, L],
va: UnaryTCConstraint[L, Va],
fp: FnToProduct.Aux[F, L => R]
) = ???
我确实有这样的感觉,简单地添加约束只能确保输入有效,但对实现函数体没有任何帮助,但我不知道如何去纠正它。
traverse
然后开始抱怨缺少证据,所以我最终得到:
def validateGen[P <: Product, F, L <: HList, R](params: P)(block: F)(
implicit
gen: Generic.Aux[P, L],
va: UnaryTCConstraint[L, Va],
tr: Traverser[L, toValidationNel.type],
fp: FnToProduct.Aux[F, L => R]
) = {
traverse(gen.to(params): HList)(toValidationNel).map(_.tupled).map(block.toProduct)
}
然而,编译器继续抱怨缺少Traverser[HList, toValidationNel.type]
隐式参数,即使它在那里。
traverse
为了编译调用,我需要向编译器提供哪些额外的证据?它是否与UnaryTCConstraint
未以对调用有用的方式声明有关traverse
,即它不能适用toValidationNel
,params
因为它不能证明params
包含 only Va[_]
?
PS我还找到了泛型类型的leftReduce Shapeless HList,并尝试使用foldRight
而不是traverse
无济于事;在尝试诊断编译器真正缺乏哪些证据时,错误消息并没有太大帮助。
更新:
根据 lmm 所指出的,我已经删除了演员表HList
,但是,现在的问题是,虽然在非通用解决方案中我可以调用调用.map(_.tupled).map(block.toProduct)
的结果,traverse
但我现在得到:
值图不是 shapeless.contrib.scalaz.Out 的成员
为什么它可能是traverse(params.productElements)(toValidationNel)
调用结果而不是通用遍历?
更新 2:
更改该Traverser[...]
位以Traverser.Aux[..., Va[L]]
帮助编译器确定遍历的预期结果类型,但这只会使validateGen
函数编译成功,但会在调用站点产生另一个错误:
[error] could not find implicit value for parameter tr: shapeless.contrib.scalaz.Traverser.Aux[L,toValidationNel.type,Va[L]]
[error] validateGen(params) { (x: String :: String :: HNil) => 3 }
[error] ^
我也觉得这UnaryTCConstraint
是完全没有必要的——但我对 Shapeless 还是太陌生了,不知道是不是这样。
更新 3:
意识到从遍历器中出来的类型不可能是Va[L]
因为L
它本身已经是 的 hlist Va[_]
,所以我将L
类型参数拆分为In
and Out
:
def validateGen[P <: Product, F, In <: HList, Out <: HList, R](params: P)(block: F)(
implicit
gen: Generic.Aux[P, In],
va: UnaryTCConstraint[In, Va], // just for clarity
tr: Traverser.Aux[In, toValidationNel.type, Va[Out]],
fn: FnToProduct.Aux[F, Out => R]
): Va[R] = {
traverse(gen.to(params))(toValidationNel).map(block.toProduct)
}
这编译得很好——我很想知道以前的版本是如何Va[L]
作为返回值(即第三个参数Traverser.Aux
)编译的——但是,在调用站点,我现在得到:
未指定值参数 tr, fn