Scala:类

时间:2017-10-19 09:53:51

标签: scala

是否有一种巧妙的方法可以将只有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?!

2 个答案:

答案 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]]等。这正是您想要实现的目标。

所以计划是:

  1. 将您的案例类转换为HList,其中包含Option个 你的情况。

  2. 使用HList上的序列操作(您将获得Option[Hlist]

  3. 映射Option以将HList转换为您的下一个案例类表示。

  4. 我本人即将自己实施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}}(一般来说,HListHList 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土地。首先,我们需要DataDataX之间的某种关系,否则我们会从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的两个版本压缩在一起来实现它,然后折叠对来弄清楚如何从一个到另一个。不过,我不会在这里实现它。