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)
)
}
}
}
source to share
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
source to share