4

我最近询问了 map 和 reduce/fold over scalaz.Validation 的 HList并得到了一个很好的答案,关于如何将固定大小的元组Va[T](它是 的别名scalaz.Validation[String, T])转换为scalaz.ValidationNel[String, T]. 从那时起,我一直在研究 Shapeless 和类型级编程,以尝试提出一种适用于任何大小的元组的解决方案。

这就是我要开始的:

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) => ... }

我开始使用 anyProduct而不是一对并将其内容限制为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,即它不能适用toValidationNelparams因为它不能证明params包含 only Va[_]

PS我还找到了泛型类型的leftReduce Shapeless HList,并尝试使用foldRight而不是traverse无济于事;在尝试诊断编译器真正缺乏哪些证据时,错误消息并没有太大帮助。

更新:

根据 lmm 所指出的,我已经删除了演员表HList,但是,现在的问题是,虽然在非通用解决方案中我可以调用调用.map(_.tupled).map(block.toProduct)的结果,traverse但我现在得到:

值图不是 shapeless.contrib.scalaz.Out 的成员

为什么它可能是traverse(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它本身已经是 的 hlist Va[_],所以我将L类型参数拆分为Inand 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)编译的——但是,在调用站点,我现在得到:

未指定值参数 tr, fn

4

2 回答 2

2

你有一个Traverser[L, toValidationNel.type]不一样的东西Traverser[HList, toValidationNel.type](它必须为任何工作HList- 没有机会)。我不知道你为什么写gen.to(params): HList,但这是在丢弃类型信息;那不应该是类型L吗?

这可能只会将问题提高一级;我怀疑您是否能够Traverser自动获得所需的内容。但是您应该能够编写一个隐式方法来提供基于 的方法UnaryTCConstraint,并且 shapeless 可能已经包含该方法,并且它将正常工作。

更新:

在第一个示例中,编译器知道Traverser它正在使用的特定实例,因此它知道Out类型是什么。因为validateGen您没有对 进行任何限制tr.Out,所以编译器无法知道它是支持的类型.map。如果您知道遍历的输出需要是什么,那么您可能需要一个适当的Traverser.Aux即:

tr: Traverser.Aux[L, toValidationNel.type, Va[L]]

(只是不要问我如何确保类型推断仍然有效)。

我想你可能想要.map(_.tupled),因为_已经有 a HList(我怀疑它在原版中validate也是多余的),但我以前从未使用.toProduct过,所以也许你做对了。

更新 2:

是的,这和我最初的怀疑一样。看看Sequencer我怀疑你是对的,并且UnaryTCConstraint将被Traverser. 如果您不使用它,那么没有必要要求它。

我能给出的唯一建议是追逐那些应该提供你暗示的电话。例如Traverser应该来自Traverser.mkTraverser. 因此,如果您尝试调用Traverser.mkTraverser[String :: String :: HNil, toValidationNel.type, Va[String] :: Va[String] :: HNil],那么您应该能够查看找不到的是 Mapper 还是 Sequencer。然后你可以通过应该发生的隐式调用进行递归,直到你找到一个更简单的情况下应该工作,但不是。

于 2014-10-21T14:07:09.213 回答
0

经过长时间的实验、挫折和死脑细胞,我从头开始没有Traverser,而是用Mapperand 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

于 2014-10-22T17:12:01.607 回答