这个答案有点来自this。
首先,这可以推广到每个Applicative 函子,所以我不会专门使用Option,除了示例。其次,这对所有 Generic/HList 好东西都使用了 shapeless。
你想sequence 超过HLists。这是作为折叠实现的。这是累加函数:
// (a, b) => a :: b plus applicative and shapeless noise
object sequenceFold extends Poly2 {
implicit def fold[A, B <: HList, F[_]: Applicative] = at[F[A], F[B]] { (fa, fb) =>
fa.map { a: A => b: B => a :: b }.ap(fb)
}
}
那么,sequenceH
def sequenceH[
F[_]: Applicative,
L <: HList,
O <: HList
](l: L)(implicit
restrict: UnaryTCConstraint[L, F], // All elements of L are F[something]
folder: RightFolder.Aux[L, F[HNil], sequenceFold.type, F[O]] // evidence for fold
): F[O] = l.foldRight(Applicative[F].pure(HNil: HNil))(sequenceFold)(folder)
// This is rather painful to use, because type inference breaks down utterly
现在,我们使用Generic 将其拖出HListland。首先,我们需要在Data 和DataX 之间建立某种关系,否则我们会从HList Int :: Int :: HNil 中查找DataX,这是行不通的。在这种情况下,我认为最好将 Data 参数化为一些构造函数 F,但我认为类型类也可以工作:
case class DataF[F[_]](a: F[Int], b: F[Int])
type Data = DataF[Option]
type Id[X] = X
type DataX = DataF[Id]
def sequenceCase[
D[F[_]], // Types like DataF
F[_]: Applicative,
IL <: HList,
OL <: HList
](i: D[F])(implicit
genI: Generic.Aux[D[F], IL], // D[F] <=> IL
restrict: UnaryTCConstraint[IL, F], // All elements of IL <=> D[F] are F[something]
folder: RightFolder.Aux[IL, F[HNil], sequenceFold.type, F[OL]], // Can sequence IL to O[OL]
genO: Generic.Aux[D[Id], OL] // OL <=> D[Id]
): F[D[Id]] = sequenceH(genI.to(i))(Applicative[F], restrict, folder).map(genO.from)
// Type inference is fixed here
Seq(DataF[Option](None , None ),
DataF[Option](Some(1), None ),
DataF[Option](None , Some(1)),
DataF[Option](Some(1), Some(1))
).map(sequenceCase(_))
// None, None, None, Some(DataF[Id](1, 1))
这可行,但如果
case class DataF2[F[_]](a: F[Int], b: String)
// b is NOT in F
这变得复杂了,因为它也可能有
case class DataF3[F[_]](a: F[Int], b: Option[String])
如果F = Option,你真的不知道该怎么做,因为获得Option[Int :: Option[String]] 和Option[Int :: String] 是有意义的。我想我会通过将两个版本的类的HLists 压缩在一起来实现它,然后将它们折叠起来以找出如何从一个到另一个。不过我不会在这里实现它。