[英]Generic transform/fold/map over tuple/hlist containing some F[_]
I recently asked Map and reduce/fold over HList of scalaz.Validation and got a great answer as to how to transform a fixed sized tuple of Va[T]
(which is an alias for scalaz.Validation[String, T]
) into a scalaz.ValidationNel[String, T]
. 我最近询问了Map并减少/折叠scalaz.Validation的HList,并得到了一个很好的答案,如何将一个固定大小的Va[T]
元组(这是scalaz.Validation[String, T]
的别名)转换为scalaz.ValidationNel[String, T]
。 I've since then been studying Shapeless and type level programming in general to try to come up with a solution that works on tuples of any size. 从那以后,我一直在研究无形和类型级编程,试图找到适用于任何大小的元组的解决方案。
This is what I'm starting out with: 这就是我刚开始的时候:
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)
}
so then validate
is a helper I call like this: 那么validate
是一个帮手,我这样称呼:
val params = (
postal |> nonEmpty[String]("no postal"),
country |> nonEmpty[String]("no country") >=> isIso2Country("invalid country")
)
validate(params) { (postal, country) => ... }
I started out by taking any Product
instead of a pair and constraining its contents to Va[T]
: 我开始采用任何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]
) = ???
I do have the feeling that simply adding the constraint only makes sure the input is valid but doesn't help at all with implementing the body of the function but I don't know how to go about correcting that. 我确实觉得简单地添加约束只能确保输入有效,但对实现函数体没有任何帮助,但我不知道如何去纠正它。
traverse
then started complaining about a missing evidence so I ended up with: 然后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)
}
The compiler however continued to complain about a missing Traverser[HList, toValidationNel.type]
implicit parameter even though it's there. 然而,编译器继续抱怨缺少Traverser[HList, toValidationNel.type]
隐式参数,即使它存在。
Which additional evidence do I need to provide to the compiler in order for the traverse
call to compile? 我需要向编译器提供哪些额外的证据才能进行traverse
调用以进行编译? Has it got to do with the UnaryTCConstraint
not being declared in a way that is useful to the traverse
call, ie it cannot apply toValidationNel
to params
because it cannot prove that params
contains only Va[_]
? 它是否与UnaryTCConstraint
没有以对traverse
调用有用的方式声明,即它不能将toValidationNel
应用于params
因为它不能证明params
只包含Va[_]
?
PS I also found leftReduce Shapeless HList of generic types and tried to use foldRight
instead of traverse
to no avail; PS我还发现了leftReduce无形HList的泛型类型并尝试使用foldRight
而不是traverse
无济于事; the error messages weren't too helpful when trying to diagnose which evidence the compiler was really lacking. 在尝试诊断编译器确实缺少哪些证据时,错误消息并没有太大帮助。
UPDATE: 更新:
As per what lmm has pointed out, I've removed the cast to HList
, however, the problem's now that, whereas in the non-generic solution I can call .map(_.tupled).map(block.toProduct)
on the result of the traverse
call, I'm now getting: 根据lmm指出的内容,我已经将转换删除到了HList
,但是现在的问题是,在非泛型解决方案中,我可以调用.map(_.tupled).map(block.toProduct)
traverse
调用的结果,我现在得到:
value map is not a member of shapeless.contrib.scalaz.Out value map不是shapeless.contrib.scalaz.Out的成员
How come it's possible that it was possible on the result of the traverse(params.productElements)(toValidationNel)
call and not the generic traverse? 为什么有可能在traverse(params.productElements)(toValidationNel)
调用的结果而不是通用遍历?
UPDATE 2: 更新2:
Changing the Traverser[...]
bit to Traverser.Aux[..., Va[L]]
helped the compiler figure out the expected result type of the traversal, however, this only makes the validateGen
function compile successfully but yields another error at the call site: 将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] ^
I'm also getting the feeling here that the UnaryTCConstraint
is completely unnecessary — but I'm still too new to Shapeless to know if that's the case. 我也觉得UnaryTCConstraint
是完全没必要的 - 但我仍然对Shapeless太新了,不知道是不是这样。
UPDATE 3: 更新3:
Having realized the type that comes out of the traverser cannot be Va[L]
because L
itself is already a hlist of Va[_]
, I've split the L
type parameter to In
and Out
: 已经意识到遍历的类型不能是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)
}
this compiles well — I'd be curious to find out how come the previous version with Va[L]
being the return value (ie the 3rd param to Traverser.Aux
) even compiled — however, at the call site, I now get: 这编译得很好 - 我很想知道以前版本的Va[L]
是如何返回值(即Traverser.Aux
的第3个参数)甚至编译 - 但是,在调用网站,我现在得到:
Unspecified value parameters tr, fn 未指定的值参数tr,fn
You have a Traverser[L, toValidationNel.type]
which is not the same thing as Traverser[HList, toValidationNel.type]
(which would have to work for any HList
- no chance). 你有一个Traverser[L, toValidationNel.type]
与Traverser[HList, toValidationNel.type]
(它必须适用于任何HList
- 没有机会)。 I don't know why you've written gen.to(params): HList
, but this is throwing away type information; 我不知道为什么你写了gen.to(params): HList
,但这会丢掉类型信息; shouldn't that be of type L
? 不应该是L
型?
This will probably only kick the problem one level higher; 这可能只会使问题更高一级; I doubt you'll be able to get the Traverser
you need automatically. 我怀疑你能否自动获得你需要的Traverser
。 But you should be able to write an implicit method that supplies one based on the UnaryTCConstraint
, and it's possible shapeless already includes that and it will Just Work. 但是你应该能够编写一个隐式方法,根据UnaryTCConstraint
提供一个方法,并且它可能是无形的,已经包含了它并且它将是Just Work。
Update: 更新:
In the first example, the compiler knew the specific Traverser
instance it was using, so it knew what the Out
type was. 在第一个示例中,编译器知道它正在使用的特定Traverser
实例,因此它知道Out
类型是什么。 In validateGen
you haven't constrained anything about tr.Out
, so the compiler has no way of knowing that it's a type that supports .map
. 在validateGen
您没有约束任何关于tr.Out
,因此编译器无法知道它是支持.map
的类型。 If you know what the output of the traverse needs to be then you can probably require an appropriate Traverser.Aux
ie: 如果你知道遍历的输出需要什么,那么你可能需要一个合适的Traverser.Aux
即:
tr: Traverser.Aux[L, toValidationNel.type, Va[L]]
(Just don't ask me how to make sure the type inference still works). (只是不要问我如何确保类型推断仍然有效)。
I think you probably don't want the .map(_.tupled)
, because the _
there is already a HList
(I suspect it's redundant in the original validate
too), but I've never used .toProduct
before so maybe you have it right. 我想你可能不想要.map(_.tupled)
,因为_
已经有一个HList
(我怀疑它在原始validate
也是多余的),但是我从来没有使用过.toProduct
,所以也许你有对的。
Update 2: 更新2:
Right, this is as I initially suspected. 对,这是我最初怀疑的。 Looking at the implementation of Sequencer
I suspect you're right and the UnaryTCConstraint
will be subsumed by the Traverser
. 综观实行Sequencer
我怀疑你是正确的和UnaryTCConstraint
将由被包含Traverser
。 If you're not using it then no point requiring it. 如果你没有使用它,那么没有必要。
The only advice I can give is to chase through the calls that should be providing your implicits. 我能给出的唯一建议是追逐应该提供你的暗示的电话。 Eg the Traverser
should be coming from Traverser.mkTraverser
. 例如, Traverser
应来自Traverser.mkTraverser
。 So if you try calling Traverser.mkTraverser[String :: String :: HNil, toValidationNel.type, Va[String] :: Va[String] :: HNil]
then you should be able to see whether it's the Mapper or the Sequencer that can't be found. 因此,如果您尝试调用Traverser.mkTraverser[String :: String :: HNil, toValidationNel.type, Va[String] :: Va[String] :: HNil]
那么您应该能够看到它是Mapper还是Sequencer无法找到。 Then you can recurse through the implicit calls that should happen until you find a simpler case of something that should be working, but isn't. 然后你可以通过隐式调用来递归,直到你找到一个应该工作的更简单的情况,但事实并非如此。
After long hours of experimentation, frustration and dead brain cells, I've started from scratch without Traverser
and instead gone with Mapper
and Sequencer
; 经过长时间的实验,挫折和死脑细胞,我从头开始没有Traverser
,而是选择Mapper
和Sequencer
; I'll later try to see if I can make it use Traverser
again (if not for practicality, at least for learning purposes): 我稍后会试着看看我是否可以再次使用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)
}
Here's proof — pun intended — that it runs http://www.scastie.org/7086 . 这是证明 - 双关语意图 - 它运行http://www.scastie.org/7086 。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.