是否有一种巧妙的方法可以将只有Option
个字段的类转换为包含在选项中的类似类?
case class Data(a: Option[Int], b: Option[Int])
case class DataX(a: Int, b: Int)
def convert(data: Data): Option[DataX] =
for {
alpha <- data.a
beta <- data.b
} yield DataX(alpha, beta)
这只是单调乏味的打字,似乎应该/可能是标准方式,例如在Cats还是Scalaz?!
答案 0 :(得分:0)
在我们开始实施之前,有两个小笔记。
1)通过无形,您可以将任何案例类(或ADT)转换为其通用表示形式并返回
- 如果是案例类 - 通用表示将是HList
- 如果是ADT(密封特征/抽象类,对象/案例类扩展它) - 通用表示将是Coproduct
你可以做这样的转换,因为它们是同构的。进行这样的转换很有用,因为HList
- 具有List的属性,所以你可以做一些很好的东西,比如映射,折叠,过滤等等。
2)函数式编程中有一个抽象,称为序列(有时更常见的是Traversable Functor),它将F[G[A]]
转换为G[F[A]]
,给定G
- 是Applicative
。因此,举例来说,您可以转换List[Option[A]]
- &gt; Option[List[A]]
,或Future[Either[?, A]]
到Either[? Future[A]]
等。这正是您想要实现的目标。
所以计划是:
将您的案例类转换为HList
,其中包含Option
个
你的情况。
使用HList
上的序列操作(您将获得Option[Hlist]
)
映射Option
以将HList
转换为您的下一个案例类表示。
我本人即将自己实施Sequencer
,但发现该部分已经实施https://github.com/typelevel/shapeless-contrib("org.typelevel" %% "shapeless-scalaz" % "0.4.0"
)。您可以查看Sequencer的实现,不要害怕,一开始它看起来像完全魔术。在查看几种类型的无形状实现之后,它开始变得有意义了。
所以非泛型实现很简单:
import shapeless.Generic
import scalaz.std.option.optionInstance
import shapeless.contrib.scalaz.functions._
def convertData(d: DataO): Option[Data] = {
val x = Generic[DataO].to(d)
val b = sequence(x)
b.map(Generic[Data].from)
}
这样我们就失去了类型,所以让我们为函数提取更多类型。
我们绝对需要提供
输入类型:I
和
输出类型:O
。
我们还需要提供它是Generic代表,它是无形的提供隐式:Generic.Aux[I, Ri]
Ri
- 将是HList,在您的情况下由Option
组成
然后你需要一个序列发生器,它将你的HList
转换为另一个Option
的{{1}}(一般来说,HList
到HList of Fs
):{{1} },其中F[HList]
- 是一个Functor。
所以最后实现如下:。
Sequencer.Aux[A, F[Ro]]
其中,F - 是任何申请人,不一定是F
。
问题是,最终,scala编译器无法很好地推断出类型,你需要手动推断它们:
def convertData[I, Ri <: HList, F[_]: Functor, O, Ro](d: I)(implicit GI: Generic.Aux[I, Ri], Se: Sequencer.Aux[Ri, F[Ro]], G: Generic.Aux[O, Ro]): F[O] = {
val x = GI.to(d)
val b = sequence(x)
val y = Functor[F].map(b)(G.from)
y
}
这是因为Option
仅适用于 case class DataO(i: Option[Int])
case class Data(i: Int)
convertData[DataO, shapeless.::[Option[Int], HNil], Option, Data, shapeless.::[Int, HNil]](
DataO(Option(2))
)
,但通用表示也可以是Sequencer
,因此您需要提供证据,通用分解将为{{1} }}。理想情况下,您只需要指定输出类型和应用类型:
HList
我仍然认为这是可能的,但我还没弄清楚如何。我会尝试稍微调整一下,也许我会找到方法。
<强> UPD 强> 我设法做到了,结束代码是:
Coproduct
答案 1 :(得分:0)
这个答案需要this。
首先,这会向每个Applicative
仿函数推广,因此除了示例之外,我不会特别使用Option
。其次,这对所有Generic
/ HList
好东西使用无形。
您希望sequence
超过HList
。这是作为折叠实现的。这是累积功能:
// (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
将其拖出HList
土地。首先,我们需要Data
和DataX
之间的某种关系,否则我们会从DataX
HList
中查找Int :: Int :: HNil
,而不是#&} 39;工作。在这种情况下,我认为最好只在一些构造函数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]
是有意义的。我想我是通过将HList
的两个版本压缩在一起来实现它,然后折叠对来弄清楚如何从一个到另一个。不过,我不会在这里实现它。