Unleash Bonfire of Lights with Lifeless?

I have a situation where I want to abstract from arity and establish a type convention between one or more "raw" types ( A

and B

below), a method that should return the corresponding Seq(Option[A], Option[B], ...)

types (named extract

below) and a set of field configurations (named configs

below) , each of which knows how to get the value of the corresponding raw type.

In the code below, ideally, I'd like to see Dimensions1

, and Dimension2

did not exist. If I had to do some kind of recursive head / tail s.c.i.List

-view construct , I could do it. :)

/***
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)
      )
    }
  }
}

      

+3


source to share


1 answer


If I understand the problem well, you need a function that asserts HList

of DimensionConfig[T]

will provide you Dimension

with type parameters All

and Configs

set to the correct types and implementation extractAll

. (Correct me if I'm wrong :-)

Thus, the dependent function of the formless should be able to provide:

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)

      

The above function defines a "pseudo" dependent function that takes an argument HList

as an argument (later on we will only accept HLists made from DimensionConfig[T]

), and returns Dimensions

with its set of type parameters (see below for definition Dimensions.Aux

- we will make an All

HList from Option[T]

corresponding input types HList).

Then we have to provide the definition of this dependent function with some values, here HLists of DimensionsConfig[T]

- this is usually done in the single function object of the dependent object:

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

      

Here we have defined DimensionsOf

in HNil

and HLists forms DimensionConfig[H] :: CT

, where CT

is an HList that DimensionsOf

is self-defined (as needed - and then used - assuming it is).



Dimensions.Aux

used in the above definitions is defined as

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

      

The function Dimensions

defined above can be used as follows:

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

and are Dimensions2

no longer needed! And you can get Dimensions

for arbitrary phenomena at the same time, for example:

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

      

+1


source







All Articles