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 of
DimensionConfig[T] would give you a Dimension with the type parameters All and Configs set to the right types and an implementation of extractAll. (Correct me if I'm wrong :-)
So a dependent function à la shapeless should be able to provide that:
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 defines a "pseudo" dependent function, that accepts a HList as argument (we will later make it accept only HLists made of DimensionConfig[T]), and returns a Dimensions with its type parameters set (see below for the definition of Dimensions.Aux - we will make All be a HList made of Option[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:
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 defined DimensionsOf at HNil, and HLists of the form DimensionConfig[H] :: CT, where CT is an HList at which DimensionsOf is it-self defined (as we require - and then use - an implicit that attests so).
Dimensions.Aux used in the above definitions is defined like
object Dimensions {
type Aux[All0 <: HList, Configs0 <: HList] = Dimensions { type All = All0; type Configs = Configs0 }
}
The dimensions function defined above can then be used this way:
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 Dimensions2 are not needed any more! And you can get Dimensions for arbitrary arities at the same time, e.g.:
val dimensions4 = dimensions(intConfig :: stringConfig :: intConfig :: stringConfig :: HNil)
val res4 = dimensions4.extractAll(JsonEventRecord()) // Option[Int] :: Option[String] :: Option[Int] :: Option[String] :: HNil
Related
I'm trying to use shapeless to do additions and removals from Hlists. But I can't seem to get it to work.
So I here are my lists:
object ShapelessExample {
import shapeless._
def main(args: Array[String]): Unit = {
case class Container[T <: Singleton](name: T)
val a = Container("A") :: Container("B") :: Container("C") :: HNil
val b = Container("B") :: Container("C") :: HNil
println {
a.removeAll[b.type] //doesn't work
}
}
}
So the removeAll method on Hlist only takes a type parameter, but I can't seem to use b.type. I can manually specify a.removeAll[Container["B"] :: Container["C"] :: HNil], but is there any way to just use b's type?
Shapeless tries to remove precisely type b.type but can't find it among Container["A"] :: Container["B"] :: Container["C"] :: HNil so #user is correct, singleton type b.type is too specific.
In order to infer an HList type from a val singleton type try to modify the method
implicit class RemoveAllOps[L <: HList](a: L) {
def removeAll[L1 <: HList](b: L1)(implicit
ra: shapeless.ops.hlist.RemoveAll[L, L1]
): ra.Out = ra(a)
}
a.removeAll(b) // (Container(B) :: Container(C) :: HNil,Container(A) :: HNil)
or
implicit class RemoveAllOps[L <: HList](a: L) {
def removeAllFrom[O <: Singleton] = new {
def apply[L1 >: O <: HList]()(implicit
ra: shapeless.ops.hlist.RemoveAll[L, L1]
): ra.Out = ra(a)
}
}
a.removeAllFrom[b.type]() //(Container(B) :: Container(C) :: HNil,Container(A) :: HNil)
Given a type dependent Converter type class which can convert a String into an Integer:
trait Converter[From] {
type To
def to(from: From): To
}
object Converter {
type Aux[From, Out0] = Converter[From] { type To = Out0 }
implicit val forInteger = new Converter[Integer] {
type To = String
def to(value: Integer) = ???
}
}
// this works
val single = the[Converter[Integer]]
implicitly[single.To =:= String]
val singleAux = the[Converter.Aux[Integer, String]]
I would like it to work for HLists, e.g. Integer :: HNil. In theory, all I need is implicits for HNil and HList:
implicit val forHNil = new Converter[HNil] {
type To = HNil
def to(value: HNil) = HNil
}
implicit def forHList[Head, HeadConverted, Tail <: HList, TailConverted <: HList](
implicit
hConverter: Converter.Aux[Head, HeadConverted],
tConverter: Converter.Aux[Tail, TailConverted]
): Converter[Head :: Tail] = new Converter[Head :: Tail] {
type To = HeadConverted :: TailConvertedHList
def to(values: Head :: Tail) = ???
}
The above works fine for HNil:
val hnil = the[Converter[HNil]]
implicitly[hnil.To =:= HNil]
val hnilAux = the[Converter.Aux[HNil, HNil]]
But not for an HList. Interestingly, it does find an instance, but does not derive the resulting type
val hlist = the[Converter[Integer :: HNil]]
type expected = String :: HNil
implicitly[hlist.To =:= expected] //fails
val hlistAux = the[Converter.Aux[Integer :: HNil, String :: HNil]] // fails
I've set it up in a self contained project which has a few more (failed) attempts to debug the issue: https://github.com/mpollmeier/shapeless-dependent-type-typeclass-problem
The problem is that I explicitly stated the return type of forHList as Converter[Head :: Tail]. That return type is incomplete, and it's missing exactly the part that I needed.
Just changing the declared return type to Converter.Aux[Head :: Tail, HeadConverted :: TailConverted] (or even just leaving it out) fixes the problem.
All credit for this answer goes to Miles Sabin, he mentioned the problem in the above comment. Thank you!
A type level foldRight works fine (getLabelWithValues), and a follow-on type level map (getValues) also works fine. If I combine both in one method (getValuesFull), it doesn't work any more though. What is the missing piece?
The full source (with sbt ready to ~run with implicit debug output) is here: https://github.com/mpollmeier/shapeless-playground/tree/8170a5b
case class Label[A](name: String)
case class LabelWithValue[A](label: Label[A], value: A)
val label1 = Label[Int]("a")
val labels = label1 :: HNil
object combineLabelWithValue extends Poly2 {
implicit def atLabel[A, B <: HList] = at[Label[A], (B, Map[String, Any])] {
case (label, (acc, values)) ⇒
(LabelWithValue(label, values(label.name).asInstanceOf[A]) :: acc, values)
}
}
object GetLabelValue extends (LabelWithValue ~> Id) {
def apply[B](labelWithValue: LabelWithValue[B]) = labelWithValue.value
}
val labelsWithValues: LabelWithValue[Int] :: HNil = getLabelWithValues(labels)
// manually mapping it works fine:
val valuesManual: Int :: HNil = labelsWithValues.map(GetLabelValue)
// using a second function with Mapper works fine:
val valuesSecondFn: Int :: HNil = getValues(labelsWithValues)
// error: could not find implicit value for parameter mapper: shapeless.ops.hlist.Mapper.Aux[Main.GetLabelValue.type,WithValues,Values]
// val valuesFull: Int :: HNil = getValuesFull(labels)
def getLabelWithValues[L <: HList, P, WithValues](labels: L)(
implicit folder: RightFolder.Aux[L, (HNil.type, Map[String, Any]), combineLabelWithValue.type, P],
ic: IsComposite.Aux[P, WithValues, _]
): WithValues = {
val state = Map("a" -> 5, "b" -> "five")
val resultTuple = labels.foldRight((HNil, state))(combineLabelWithValue)
ic.head(resultTuple)
}
def getValues[WithValues <: HList, Values <: HList](withValues: WithValues)(
implicit mapper: Mapper.Aux[GetLabelValue.type, WithValues, Values]
): Values = {
withValues.map(GetLabelValue)
}
def getValuesFull[L <: HList, P, WithValues <: HList, Values <: HList](labels: L)(
implicit folder: RightFolder.Aux[L, (HNil.type, Map[String, Any]), combineLabelWithValue.type, P],
ic: IsComposite.Aux[P, WithValues, _],
mapper: Mapper.Aux[GetLabelValue.type, WithValues, Values]
): Values = {
val state = Map("a" -> 5, "b" -> "five")
val resultTuple = labels.foldRight((HNil, state))(combineLabelWithValue)
val withValues: WithValues = ic.head(resultTuple)
withValues.map(GetLabelValue)
}
The issue here is that you're ending up trying to map over an HList where the HNil is statically typed as HNil.type. This doesn't work in general—e.g. in a simplified case like this:
import shapeless._, ops.hlist.Mapper
val mapped1 = Mapper[poly.identity.type, HNil]
val mapped2 = Mapper[poly.identity.type, HNil.type]
mapped1 will compile, but mapped2 won't.
The trick is to change the HNil.type in your RightFolder types to HNil and then to call foldRight with HNil: HNil. This will make everything work just fine.
There are a few other suggestions I'd make (destructure the tuple in place of P instead of using IsComposite, skip the Aux on mapper and return mapper.Out instead of having a Value type parameter, etc.), but they're probably out of the scope of this question.
Is it possible to rewrite the following example using only polymorphic functions without TypeTags? The example consists of a class A[T] which has a method matches returning true when applied to an instance of A with the same type parameter T and false if this type parameter has a different value. Then matches is mapped over an hlist l of A[T] twice resulting in an hlist of nested hlists containing results of matching each item of l against others:
import scala.reflect.runtime.universe._
import shapeless._
class A[T: TypeTag]{
object matches extends Poly1 {
implicit def default[U: TypeTag] = at[A[U]]{ _ => typeOf[U] <:< typeOf[T] }
}
}
val l = new A[Int] :: new A[String] :: new A[Boolean] :: HNil
object matcher extends Poly1 {
implicit def default[T] = at[A[T]]{ a => l map a.matches }
}
l map matcher
Each item has one match, i.e. the result is:
(true :: false :: false :: HNil) ::
(false :: true :: false :: HNil) ::
(false :: false :: true :: HNil) :: HNil
When I try to rewrite the example without TypeTags, matches always uses it's no case and return false:
import shapeless._
class A[T]{
object matches extends Poly1 {
implicit def yes = at[A[T]]{_ => true}
implicit def no[U] = at[U]{_ => false}
}
}
val l = new A[Int] :: new A[String] :: new A[Boolean] :: HNil
object matcher extends Poly1 {
implicit def default[T] = at[A[T]]{ a => l map a.matches }
}
l map matcher
The result is:
(false :: false :: false :: HNil) ::
(false :: false :: false :: HNil) ::
(false :: false :: false :: HNil) :: HNil
Is it possible to rewrite this example without TypeTags and get the same result as in the first case?
It looks like you really want to be able to partially apply higher rank functions to solve this problem cleanly. This isn't possible with any kind of nice syntax out of the box, but I once wrote some code to help make it a little easier (note that this is all 1.2.4):
import shapeless._
trait ApplyMapper[HF, A, X <: HList, Out <: HList] {
def apply(a: A, x: X): Out
}
object ApplyMapper {
implicit def hnil[HF, A] = new ApplyMapper[HF, A, HNil, HNil] {
def apply(a: A, x: HNil) = HNil
}
implicit def hlist[HF, A, XH, XT <: HList, OutH, OutT <: HList](implicit
pb: Poly.Pullback2Aux[HF, A, XH, OutH],
am: ApplyMapper[HF, A, XT, OutT]
) = new ApplyMapper[HF, A, XH :: XT, OutH :: OutT] {
def apply(a: A, x: XH :: XT) = pb(a, x.head) :: am(a, x.tail)
}
}
See the answer linked above for some context.
This allows you to write the following:
class A[T]
object matches extends Poly2 {
implicit def default[T, U](implicit sub: U <:< T = null) =
at[A[T], A[U]]((_, _) => Option(sub).isDefined)
}
object mapMatcher extends Poly1 {
implicit def default[T, X <: HList, Out <: HList](
implicit am: ApplyMapper[matches.type, A[T], X, Out]
) = at[(A[T], X)] { case (a, x) => am(a, x) }
}
val l = new A[Int] :: new A[String] :: new A[Boolean] :: HNil
There may be a nicer solution, but at the moment I only have a minute to respond, and it works as is:
scala> l.zip(l mapConst l).map(mapMatcher).toList.foreach(println)
true :: false :: false :: HNil
false :: true :: false :: HNil
false :: false :: true :: HNil
As desired.
Having
class A
class B extends A
class C extends A
class Container[+L <: HList](l: L)
what is shapeless way to code as follows?:
def foo[L <: HList](a: A): Container[L] = a match {
case (b: B) => new Container(1 :: "a" :: HNil)
case (c: C) => new Container(1.0 :: HNil)
case _ => new Container(HNil)
}
and then to use it in a way:
val l1: Container[Int :: String :: HNil] = foo(new B)
val l2: Container[Double :: HNil] = foo(new C)
val l3: Container[String :: HNil] = foo(new C) // Compile-time error
NOTE that the way above is principally incorrect because of reasons similar to ones described at "Why `List[B]` is not a subtype of `Seq[L]` when `class B extends A` and `L <: A`?".
You can use shapeless' polymorphic functions for this:
// Base case
class LowPrioFoo extends Poly1 {
implicit def default[X] = at[X] { _ => new Container(HNil) }
}
// Specific cases
object foo extends LowPrioFoo {
implicit def atB = at[B] { _ => new Container(1 :: "a" :: HNil) }
implicit def atC = at[C] { _ => new Container(1.0 :: HNil) }
}
Now you can call the function foo on whatever you need:
val x = foo(new A): Container[HNil]
val y = foo(new B): Container[Int :: String :: HNil]
val z = foo(new C): Container[Double :: HNil]
This is essentially the same than you did, but it is encapsulated by foo (and interfaces nicer with shapeless). This way you can make sure no unintended conversion happens.
ADDENDUM
As #MilesSabin pointed out, there is not much use for shapeless' polymorphic functions, if the value of the argument is not used. A simple type class based solution is probably better. Such a solution is given as follows:
trait Foo[T] {
type R
def result: R
}
trait LowPrioFoo {
implicit def default[X] = new Foo[X] {
type R = Container[HNil]
val result = new Container(HNil)
}
}
object Foo extends LowPrioFoo {
implicit val bFoo = new Foo[B] {
type R = Container[Int :: String :: HNil]
val result = new Container(1 :: "a" :: HNil)
}
implicit val cFoo = new Foo[C] {
type R = Container[Double :: HNil]
val result = new Container(1.0 :: HNil)
}
}
def foo[A](x: A)(implicit f: Foo[A]): f.R = f.result
Note that this is already very close to the inner workings of Poly. Compare trait Foo to traits CaseAux and Poly#Case which model parameters as HList and allow result to depend on the actual value. This makes Foo a special case of these type classes.
Finally understood the problem essence... Seems like as follows:
implicit def fromA(a: A): Container[HNil] = new Container(HNil)
implicit def fromB(b: B): Container[Int :: String :: HNil] = new Container(1 :: "a" :: HNil)
implicit def fromC(c: C): Container[Double :: HNil] = new Container(1.0 :: HNil)
Then it is eligible to write:
val l1: Container[Int :: String :: HNil] = new B
val l2: Container[Double :: HNil] = new C
// val l3: Container[String :: HNil] = new C // Compile-time error
val l4: Container[HNil] = new A
Any better solutions are welcome.