我最近问了
Map and reduce/fold over HList of scalaz.Validation,并得到了一个很好的答案,如何将一个固定大小的Va [T]元组(scalaz.Validation [String,T]的别名)转换为scalaz.ValidationNel [String,T].从那以后,我一直在研究无形和类型级编程,试图找到适用于任何大小的元组的解决方案.
这就是我刚开始的时候:
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)
}
那么验证是一个帮手,我这样称呼:
val params = (
postal |> nonEmpty[String]("no postal"),
country |> nonEmpty[String]("no country") >=> isIso2Country("invalid country")
)
validate(params) { (postal, country) => ... }
我开始采用任何产品而不是一对,并将其内容限制为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]隐式参数,即使它存在.
我需要向编译器提供哪些额外的证据才能进行遍历调用以进行编译?它是否与UnaryTCConstraint没有以对遍历调用有用的方式声明,即它不能将VALinNel应用于params,因为它不能证明params只包含Va [_]?
附:我也找到了leftReduce Shapeless HList of generic types,并尝试使用foldRight而不是遍历无济于事;在尝试诊断编译器确实缺少哪些证据时,错误消息并没有太大帮助.
更新:
根据lmm指出的内容,我已经将转换删除到了HList,但是现在的问题是,在非泛型解决方案中,我可以调用.map(_.tupled).map(block.toProduct)遍历调用的结果,我现在得到:
value map is not a member of shapeless.contrib.scalaz.Out
为什么有可能在遍历(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的第三个参数)甚至编译 – 但是,在调用网站,我现在得到:
Unspecified value parameters tr, fn
最佳答案 你有一个Traverser [L,toValidationNel.type]与Traverser [HList,toValidationNel.type]不同(它必须适用于任何HList – 没有机会).我不知道为什么你写了gen.to(params):HList,但这会丢掉类型信息;不应该是L型?
这可能只会使问题更高一级;我怀疑你能否自动获得你需要的Traverser.但是你应该能够编写一个隐式方法,根据UnaryTCConstraint提供一个方法,并且它可能是无形的,已经包含了它并且它将是Just Work.
更新:
在第一个示例中,编译器知道它正在使用的特定Traverser实例,因此它知道Out类型是什么.在validateGen中,您没有约束任何关于tr.Out的内容,因此编译器无法知道它是支持.map的类型.如果你知道遍历的输出需要什么,那么你可能需要一个合适的Traverser.Aux,即:
tr: Traverser.Aux[L, toValidationNel.type, Va[L]]
(只是不要问我如何确保类型推断仍然有效).
我想你可能不想要.map(_.tupled),因为_已经有一个HList(我怀疑它在原始验证中也是多余的),但是我从来没有使用过.toProduct,所以也许你有对的.
更新2:
对,这是我最初怀疑的.看看Sequencer的实现,我怀疑你是对的,并且Traaryer会将UnaryTCConstraint包含在内.如果你没有使用它,那么没有必要.
我能给出的唯一建议是追逐应该提供你的暗示的电话.例如. Traverser应该来自Traverser.mkTraverser.因此,如果您尝试调用Traverser.mkTraverser [String :: String :: HNil,toValidationNel.type,Va [String] :: Va [String] :: HNil],那么您应该能够看到它是Mapper还是Sequencer无法找到.然后你可以通过隐式调用来递归,直到你找到一个应该工作的更简单的情况,但事实并非如此.