I have a situation where I want to abstract over arity, and establish type agreement between one or more "raw" types (A
and B
below), a method that should return a corresponding Seq(Option[A], Option[B], ...)
of those types (named extract
below), and a set of field configurations (named configs
below), each of which knows how to get a value of the corresponding "raw" type.
In the code below, ideally I'd like Dimensions1
and Dimension2
to not exist. If I had to do some kind of s.c.i.List
-like recursive head/tail construct, I would be OK with that. :)
/***
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)
)
}
}
}
If I understand the problem well, you would like a function that given an
HList
ofDimensionConfig[T]
would give you aDimension
with the type parametersAll
andConfigs
set to the right types and an implementation ofextractAll
. (Correct me if I'm wrong :-)So a dependent function à la shapeless should be able to provide that:
The above defines a "pseudo" dependent function, that accepts a
HList
as argument (we will later make it accept only HLists made ofDimensionConfig[T]
), and returns aDimensions
with its type parameters set (see below for the definition ofDimensions.Aux
- we will makeAll
be a HList made ofOption[T]
matching the input HList types).We then have to provide a definition of this dependent function at some values, here HLists made of
DimensionsConfig[T]
- this is typically made in the dependent function trait's singleton object:Here we defined
DimensionsOf
atHNil
, and HLists of the formDimensionConfig[H] :: CT
, whereCT
is an HList at whichDimensionsOf
is it-self defined (as we require - and then use - an implicit that attests so).Dimensions.Aux
used in the above definitions is defined likeThe
dimensions
function defined above can then be used this way:Dimensions1
andDimensions2
are not needed any more! And you can getDimensions
for arbitrary arities at the same time, e.g.: