简体   繁体   English

包含一些F [_]的元组/ hlist上的通用转换/折叠/映射

[英]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类型参数拆分为InOut

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 ,而是选择MapperSequencer ; 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM