我最近问了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)
}
所以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
,但问题现在是,而在非通用解决方案中,我可以调用{{1 \ n}关于.map(_.tupled).map(block.toProduct)
电话的结果,我现在得到:
value map不是shapeless.contrib.scalaz.Out
的成员
怎么可能在traverse
调用的结果而不是通用遍历?
更新2:
将traverse(params.productElements)(toValidationNel)
位更改为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] ^
完全没有必要 - 但我对于Shapeless来说还是太新了,不知道是不是这样。
更新3:
意识到遍历者的类型不能是UnaryTCConstraint
,因为Va[L]
本身已经是L
的hlist,我已经将Va[_]
拆分了类型参数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]
的第3个参数)甚至编译 - 但是,在呼叫网站,我现在得到:
未指定的值参数tr,fn
答案 0 :(得分:2)
你有一个Traverser[L, toValidationNel.type]
与Traverser[HList, toValidationNel.type]
不同(它必须适用于任何HList
- 没有机会)。我不知道你为什么要写gen.to(params): HList
,但这会丢掉类型信息;不应该是类型L
?
这可能只会将问题提高一级;我怀疑你是否能够自动获得你需要的Traverser
。但是你应该能够编写一个隐式方法,根据UnaryTCConstraint
提供一个方法,并且它可能没有形状已经包含了它,它就会工作。
更新
在第一个示例中,编译器知道它正在使用的特定Traverser
实例,因此它知道Out
类型是什么。在validateGen
中,您没有约束tr.Out
的任何内容,因此编译器无法知道它是支持.map
的类型。如果您知道遍历的输出需要什么,那么您可能需要适当的Traverser.Aux
,即:
tr: Traverser.Aux[L, toValidationNel.type, Va[L]]
(只是不要问我如何确保类型推断仍然有效)。
我认为您可能不想要.map(_.tupled)
,因为_
已经有HList
(我怀疑它是&#39;}在原版validate
中也是多余的,但我之前从未使用.toProduct
,所以也许你做对了。
更新2:
是的,这是我最初怀疑的。查看Sequencer
的实施情况,我怀疑您是对的,而UnaryTCConstraint
将被Traverser
包含在内。如果你没有使用它,那么就没有必要了。
我能给出的唯一建议是追逐应该提供你的暗示的电话。例如。 Traverser
应该来自Traverser.mkTraverser
。因此,如果您尝试拨打Traverser.mkTraverser[String :: String :: HNil, toValidationNel.type, Va[String] :: Va[String] :: HNil]
,那么您应该能够看到它是无法找到的Mapper还是Sequencer。然后你可以通过隐式调用来递归,直到你找到一个应该有用的更简单的情况,但不是。
答案 1 :(得分:0)
经过长时间的实验,沮丧和死脑细胞,我从头开始没有Traverser
而是与Mapper
和Sequencer
一起;我稍后会试着看看我是否可以再次使用Traverser
(如果不是为了实用性,至少出于学习目的):
def validate[P <: Product, F, L1 <: HList, L2 <: HList, L3 <: HList, R](params: P)(block: F)(
implicit
gen: Generic.Aux[P, L1],
mp: Mapper.Aux[toValidationNel.type, L1, L2],
seq: Sequencer.Aux[L2, VaNel[L3]],
fn: FnToProduct.Aux[F, L3 => R]
): VaNel[R] = {
sequence(gen.to(params).map(toValidationNel)).map(block.toProduct)
}
这里有证据 - 双关语意图 - 它运行http://www.scastie.org/7086。