Тушить костер Артерии с помощью Shapeless?
У меня есть ситуация, когда я хочу абстрагироваться над арностью и установить соглашение о типе между одним или несколькими "необработанными" типами (A
а также B
ниже), метод, который должен вернуть соответствующий 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)
)
}
}
}
1 ответ
Если я хорошо понимаю проблему, вы хотели бы функцию, которая дает HList
изDimensionConfig[T]
даст вам Dimension
с параметрами типа All
а также Configs
установить правильные типы и реализацию extractAll
, (Поправьте меня если я ошибаюсь:-)
Таким образом, зависимая функция бесформенная должна быть в состоянии обеспечить, что:
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)
Выше определено "псевдо" зависимая функция, которая принимает HList
в качестве аргумента (позже мы сделаем так, чтобы он принимал только DimensionConfig[T]
) и возвращает Dimensions
с установленными параметрами типа (определение Dimensions.Aux
- мы сделаем All
быть HList из Option[T]
соответствие входных типов HList).
Затем мы должны дать определение этой зависимой функции при некоторых значениях, здесь HLists сделаны из DimensionsConfig[T]
- обычно это делается в одноэлементном объекте зависимой функции:
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
в HNil
и 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