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

时间:2014-10-21 13:03:02

标签: scala scalaz shapeless scalaz7 type-level-computation

我最近问了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[_]拆分了类型参数LIn

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

2 个答案:

答案 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而是与MapperSequencer一起;我稍后会试着看看我是否可以再次使用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