【发布时间】:2014-10-21 13:03:02
【问题描述】:
我最近问过Map and reduce/fold over HList of scalaz.Validation,并得到了一个很好的答案,关于如何将固定大小的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) => ... }
我开始使用任何Product 而不是一对并将其内容限制为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 仅包含Va[_] ?
P.S. 我还找到了leftReduce Shapeless HList of generic types 并尝试使用foldRight 而不是traverse 无济于事;在尝试诊断编译器真正缺乏哪些证据时,错误消息并没有太大帮助。
更新:
根据 lmm 所指出的,我已将演员表移至 HList,但是,问题出在现在,而在非通用解决方案中,我可以在 traverse 的结果上调用 .map(_.tupled).map(block.toProduct)打电话,我现在得到:
值图不是 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本身已经是Va[_]的一个hlist,我已经将L类型参数拆分为In和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
【问题讨论】:
标签: scala scalaz shapeless scalaz7 type-level-computation