3

我有一种情况,我想抽象一些,并在一个或多个“原始”类型(AB以下)之间建立类型协议,一个应该返回Seq(Option[A], Option[B], ...)这些类型对应的方法(如下命名extract),以及一组字段配置(命名configs如下),每个都知道如何获取相应“原始”类型的值。

在下面的代码中,理想情况下我想要Dimensions1并且Dimension2不存在。如果我必须做某种类似s.c.i.List递归的头/尾构造,我会同意的。:)

/***
scalaVersion := "2.11.4"

libraryDependencies := Seq("com.chuusai" %% "shapeless" % "2.0.0")
*/
object Main extends App {
  import shapeless._

  case class JsonEventRecord()

  case class DimensionConfig[T](name: String,
                                valueSource: JsonEventRecord => Option[T]) {
    def extract(rec: JsonEventRecord): Option[T] = {
      valueSource(rec)
    }
  }

  trait Dimensions {
    type All <: HList // I'd like to constrain this to (Option[A], Option[B], ...)
    type Configs <: HList // I'd like to constrain this to (DimensionConfig[A],     DimensionConfig[B], ...)

    def configs: Configs
    def extractAll(rec: JsonEventRecord): All
  }

  // I'd like this to not exist :)
  trait Dimensions1 extends Dimensions {
    type A

    type All = Option[A] :: HNil
    type Configs = DimensionConfig[A] :: HNil

    val config1: DimensionConfig[A]
    def configs = config1 :: HNil

    override def extractAll(rec: JsonEventRecord): All = HList(config1.extract(rec))
  }

  // I'd like this to not exist :)
  trait Dimensions2 extends Dimensions {
    type A
    type B

    type All = Option[A] :: Option[B] :: HNil
    type Configs = DimensionConfig[A] :: DimensionConfig[B] :: HNil

    val config1: DimensionConfig[A]
    val config2: DimensionConfig[B]
    def configs = config1 :: config2 :: HNil

    override def extractAll(rec: JsonEventRecord): All = {
      HList(
        config1.extract(rec),
        config2.extract(rec)
      )
    }
  }
}
4

1 回答 1

1

如果我很好地理解了这个问题,你会想要一个给定 anHList的 函数,它DimensionConfig[T]会给你一个Dimension类型参数AllConfigs设置为正确的类型和一个extractAll. (如我错了请纠正我 :-)

所以一个依赖函数 à la shapeless 应该能够提供:

trait DimensionsOf[L <: HList] extends DepFn1[L] {
  type All <: HList
  type Out = Dimensions.Aux[All, L]
}

def dimensions[L <: HList](configs: L) 
 (implicit dimensionsOf: DimensionsOf[L]): dimensionsOf.Out =
  dimensionsOf(configs)

上面定义了一个“伪”依赖函数,它接受 aHList作为参数(我们稍后将使其仅接受由 组成的 HLists DimensionConfig[T]),并返回 aDimensions及其类型参数集(参见下面的定义Dimensions.Aux- 我们将使其All成为 HList由Option[T]匹配输入的 HList 类型组成)。

然后我们必须在某些值处提供这个依赖函数的定义,这里是由 HLists 组成的DimensionsConfig[T]——这通常是在依赖函数 trait 的单例对象中完成的:

object DimensionsOf {
  type Aux[L <: HList, All0 <: HList] = DimensionsOf[L] { type All = All0 }

  implicit val dimensionsOfHNil: DimensionsOf.Aux[HNil, HNil] =
    new DimensionsOf[HNil] {
      type All = HNil
      def apply(l: HNil) = new Dimensions {
        type All = HNil
        type Configs = HNil
        val configs = HNil
        def extractAll(rec: JsonEventRecord) = HNil
      }
    }

    implicit def dimensionsOfHCons[H, CT <: HList]
     (implicit dimensionsOfTail: DimensionsOf[CT]): Aux[DimensionConfig[H] :: CT, Option[H] :: dimensionsOfTail.All] =
      new DimensionsOf[DimensionConfig[H] :: CT] {
        type All = Option[H] :: dimensionsOfTail.All
        def apply(l: DimensionConfig[H] :: CT) = new Dimensions {
          type All = Option[H] :: dimensionsOfTail.All
          type Configs = DimensionConfig[H] :: CT
          val configs = l
          val tailDimensions = dimensionsOfTail(l.tail)
          def extractAll(rec: JsonEventRecord) = l.head.extract(rec) :: tailDimensions.extractAll(rec)
        }
      }
  }

在这里,我们定义DimensionsOf了 atHNil和 HLists 的形式DimensionConfig[H] :: CT,其中CT是一个 HList,它DimensionsOf是它自己定义的(正如我们所需要的 - 然后使用 - 一个隐式证明了这一点)。

Dimensions.Aux上述定义中使用的定义如下

object Dimensions {
  type Aux[All0 <: HList, Configs0 <: HList] = Dimensions { type All = All0; type Configs = Configs0 }
}

dimensions然后可以这样使用上面定义的函数:

val intConfig = DimensionConfig[Int]("Int", _ => Some(2))
val stringConfig = DimensionConfig[String]("String", _ => Some("a"))

val dimensions1 = dimensions(intConfig :: HNil)
val res1 = dimensions1.extractAll(JsonEventRecord()) // Option[Int] :: HNil

val dimensions2 = dimensions(intConfig :: stringConfig :: HNil)
val res2 = dimensions2.extractAll(JsonEventRecord()) // Option[Int] :: Option[String] :: HNil

Dimensions1并且Dimensions2不再需要了!您可以同时获得Dimensions任意数量,例如:

val dimensions4 = dimensions(intConfig :: stringConfig :: intConfig :: stringConfig :: HNil)
val res4 = dimensions4.extractAll(JsonEventRecord()) // Option[Int] :: Option[String] :: Option[Int] :: Option[String] :: HNil
于 2014-11-19T00:29:39.550 回答