I am trying to make this work in a type-safe manner:
val rows = db.select( ID_COLUMN, STR("name"), INT("count") ). from("tablename") ......
for ( (id, name, count) <- rows ) {
//some code to use the individual values
}
I have not found, so far, another way of making this type-safe besides the shapeless.
I do know about slick and other ORMs, so please don't send me there.
HList seems to be the way to approach passing in a heterogeneous bunch of objects and get back a list of values with specific types.
I tried to do something like this :
trait ColDef [X] {
def colName
def valalue (m:Map[String, Object]):X
}
object XS extends ColDef[String]{
def colName = "s"
def value (m:Map[String, Object]) = m("s").asInstanceOf[String]
}
object XI extends ColDef[Integer]{
def colName = "i"
def value (m:Map[String, Object]) = m("i").asInstanceOf[Integer]
}
val xhl = XS::XI::HNil
val data:Map[String,Object] = Map(("s" ->"asdf"), ("i" -> new Integer(5)))
object p1 extends Poly1 {
implicit def getValue[T, S <% ColDef[T]] = at[S] (coldef => coldef.value(data) )
}
val res = xhl map p1
val (s, i) = res.tupled //this works, giving s:String, and i:Integer
//but following does not compile
def nextstep(hl : HList, data:Map[String,Object]) = {
hl map p1
}
Just to reiterate what is essential:
HList/Shapeless are likely candidates for solving the problem, but are not the goal of this exercise. My goal is to have the return type of a function correspond to the variable types and number of parameters passed in.
It would be ideal if the user of my little utility did not need to know about HList, but that is not a real requirement.
The essential part is having the type of the result match the type of params :
val param1 = new Conf[String]
val param2 = new Conf[Integer]
... etc ....
val res = function(param1, param2, param3)
More precisely payload types of the parameters above, so that the type of res is T(String, Integer, ....) .
Let me add another clarification. I want to create a method for arbitrary arity and avoid creating a function for each count of parameters. If I was ok with 22 methods, it would look something like this:
def f[A](a:ColDef[A]):(A)
def f[A,B](a:ColDef[A], b:ColDef[B]):(A,B)
def f[A,B,C](a:ColDef[A], b:ColDef[B],c:ColDef[C]):(A,B,C)
..... and so on
And then I would not need shapeless or HList as all the possible tuples would be explicitly defined.
Actually looking at those 3 signatures - making them 22 would take a bit of time, but would avoid shapeless dependency an their implementations would also be one-liners. May be I should just spend 30 minutes and do it manually (or with a little script).
Have it work with HList input and output
You just need some minor adjustments to the definition of nextstep:
def nextstep[L <: HList](hl: L, data: Map[String, Object])(implicit mapper: Mapper[p1.type, L]): mapper.Out = {
hl map p1
}
I made the exact type of the HList L a type argument, and I required the implicit needed by map (see map's definition).
Then you (or your user) can simply call
nextstep(XS :: XI :: HNil, data)
and they will get a String :: Integer :: HNil as return type. It works as expected for any HList of ColDef[...] (returning an HList of the result).
Have it work with a tuple as input (and output HList)
In order to have it return a tuple instead of an HList, you can define it this way:
import shapeless.ops.hlist.{Tupler, Mapper}
def nextstep[L <: HList, OutL <: HList, Out](hl: L, data: Map[String, Object])(implicit mapper: Mapper.Aux[p1.type, L, OutL], tupler: Tupler.Aux[OutL, Out]): Out = {
tupler(hl map p1)
}
nextstep(XS :: XI :: HNil, data) will then return a (String, Integer), and nextstep will return the rightly typed tuple in the general case.
Tuple as input and output
The final step to accept a tuple of ColDef as input and return a tuple as output looks like:
def nextstep[P, L <: HList, OutL <: HList, Out](c: P, data: Map[String, Object])(implicit gen: Generic.Aux[P, L], mapper: Mapper.Aux[p1.type, L, OutL], tupler: Tupler.Aux[OutL, Out]): Out = {
tupler(gen.to(c) map p1)
}
The logic here is very similar to the functions defined in shapeless.syntax.std.TupleOps: converting the tuple to an HList, processing the HList, convert the output to a tuple.
Related
I'm trying to use shapeless to derive a Generic for a type member defined in a trait, but am not having luck. I have made as simple of a reproduction of the issue as I can think of while keeping it close enough to the original code. I'm taking inspiration from this blog post, and trying to expand (bastardize) it to be a bit more generic. It probably won't make sense why I have code that looks like this from this example alone, but hopefully that doesn't take away from this question :)
I have a trait that declares a type member, a case class representing some common set of fields, and another wrapper case class that combines an instance of both:
object A {
trait TheTrait {
type TheType
}
case class CommonFields(height: Double, isTall: Boolean)
case class Wrapper[T <: TheTrait](commonFields: CommonFields, t: T#TheType)
}
I also have an implementation of the trait:
trait Obj extends TheTrait
object Obj extends Obj {
case class Source(name: String, other: Int)
override type TheType = Source
}
My goal is to be able to take a tuple with values for both CommonFields and TheTrait#TheType for some instance of TheTrait, and to use shapeless to turn that into an instance of a Wrapper. So for the example so far, I'd like to go from something like (5.1, false, "sub", 10) to Wrapper[Obj](CommonFields(5.1, false), Source("other", 10)). Here's what I've come up with:
object Test {
class Constructor[T <: TheTrait] {
// take a tuple of all the fields of CommonFields and T#Trait and produce an instance of each in a A.Wrapper
def apply[In <: Product, All <: HList, ORep <: HList, CRep <: HList, N <: Nat](in: In)(implicit
cGen: Generic.Aux[A.CommonFields, CRep], // generic for CommonFields
cLen: Length.Aux[CRep, N], // the length of the CommonFields generic HList
trGen: Generic.Aux[T#TheType, ORep], // generic for T#TheType
iGen: Generic.Aux[In, All], // generic for input tuple
split: Split.Aux[All, N, CRep, ORep] // the input tuple, split at N, produces HLists for the CommonFields generic rep as well as for the T#TheType generic rep
): A.Wrapper[T] = {
val all = iGen.to(in)
val (cFields, tFields) = split(all)
val com = cGen.from(cFields)
val tr = trGen.from(tFields)
A.Wrapper(com, tr)
}
}
def construct[T <: TheTrait] = new Constructor[T]
println(construct[Obj](5.1, false, "sub", 10))
}
Unfortunately, the correct implicits cannot be found, in particular I see the following error: No implicit arguments of type: hlist.Split.Aux[Double :: Boolean :: String :: Int :: HNil, Succ[Succ[shapeless.nat._0]], Double :: Boolean :: HNil, HNil]
It seems like it is finding the right generic representation for CommonFields (by the appearances of Double :: Boolean :: HNil in the error), but cannot tell what the TheType should be. Is this asking too much of shapeless/the scala compiler? Can I give more type hints somewhere? Is there another way to achieve something like this? I can try to expand on why I have created a type structure like this if that would be helpful. Any ideas are appreciated!
EDIT:
Just to experiment, I made a variation using path dependent typing instead of the type projection, but still was unable to get it to work:
object Test {
import A._
class Constructor[T <: TheTrait] {
// take a tuple of all the fields of CommonFields and T#Trait and produce an instance of each in a A.Wrapper
def apply[In <: Product, All <: HList, ORep <: HList, CRep <: HList, N <: Nat](in: In, t: T /* <=== now takes a `T` instance */)(implicit
cGen: Generic.Aux[CommonFields, CRep], // generic for CommonFields
cLen: Length.Aux[CRep, N], // the length of the CommonFields generic HList
trGen: Generic.Aux[t.TheType, ORep], // generic for T#TheType <==== no more type projection
iGen: Generic.Aux[In, All], // generic for input tuple
split: Split.Aux[All, N, CRep, ORep] // the input tuple, split at N, produces HLists for the CommonFields generic rep as well as for the T#TheType generic
): Wrapper[T] = {
val all = iGen.to(in)
val (cFields, tFields) = split(all)
val com = cGen.from(cFields)
val tr = trGen.from(tFields)
Wrapper(com, tr)
}
}
def construct[T <: TheTrait] = new Constructor[T]
println(
construct[Obj]((5.1, false, "sub", 10), Obj) // <== passing a `TheTrait` instance
)
}
But still seeing the error
No implicit arguments of type: hlist.Split.Aux[Double :: Boolean :: String :: Int :: HNil, Succ[Succ[shapeless.nat._0]], Double :: Boolean :: HNil, HNil]
EDIT 2:
Rearranging the implicits has helped a tad. Instead of the compiler believing that ORep is HNil, it is now at least looking for it to match String :: Int :: HNil:
class Constructor[T <: TheTrait] {
// take a tuple of all the fields of CommonFields and T#Trait and produce an instance of each in a A.Wrapper
def apply[In <: Product, All <: HList, ORep <: HList, CRep <: HList, N <: Nat](in: In, t: T)(implicit
cGen: Generic.Aux[CommonFields, CRep], // generic for CommonFields
cLen: Length.Aux[CRep, N], // the length of the CommonFields generic HList
iGen: Generic.Aux[In, All], // generic for input tuple
split: Split.Aux[All, N, CRep, ORep], // the input tuple, split at N, produces HLists for the CommonFields generic rep as well as for the T#TheType generic
trGen: Generic.Aux[t.TheType, ORep] // generic for T#TheType
): Wrapper[T] = {
val all = iGen.to(in)
val (cFields, tFields) = split(all)
val com = cGen.from(cFields)
val tr = trGen.from(tFields)
Wrapper(com, tr)
}
}
def construct[T <: TheTrait] = new Constructor[T]
The error now is No implicit arguments of type: Generic.Aux[B.Obj.TheType, String :: Int :: HNil], which feels like progress to me.
I can now actually get the program to compile by explicitly creating an instance of the Generic it is looking for and making it available implicitly:
implicit val objTypeGen: Generic.Aux[Obj.TheType, String :: Int :: HNil] = Generic.instance[Obj.TheType, String :: Int :: HNil](
t => t.name :: t.other :: HNil,
{
case n :: o :: HNil => Obj.Source(n, o)
}
)
But now I have removed all of the ergonomics I had originally set out to build. Hopefully there's enough hints here though for someone to figure out how to not need to explicitly pass a TheTrait instance or define the Generic manually?
Your original code compiles as soon as you move override type TheType = Source from object Obj to trait Obj. Note that construct[Obj](..) refers to the type (trait) Obj, not the singleton type corresponding to the object Obj, therefore in your code Obj#TheType cannot be resolved to a particular type, since it remains abstract.
If you really need override type TheType = Source in object Obj, the invocation construct[Obj.type](..) will also compile.
Edit. Full code:
import shapeless.ops.hlist.{Length, Split}
import shapeless.{Generic, HList, Nat}
object Programme {
object A {
trait TheTrait {
type TheType
}
case class CommonFields(height: Double, isTall: Boolean)
case class Wrapper[T <: TheTrait](commonFields: CommonFields, t: T#TheType)
}
import A.TheTrait
trait Obj extends TheTrait {
override type TheType = Obj.Source
}
object Obj extends Obj {
case class Source(name: String, other: Int)
}
object Test {
class Constructor[T <: TheTrait] {
def apply[In <: Product, All <: HList, ORep <: HList, CRep <: HList, N <: Nat](in: In)(implicit
cGen: Generic.Aux[A.CommonFields, CRep],
cLen: Length.Aux[CRep, N],
trGen: Generic.Aux[T#TheType, ORep],
iGen: Generic.Aux[In, All],
split: Split.Aux[All, N, CRep, ORep]
): A.Wrapper[T] = {
val all = iGen.to(in)
val (cFields, tFields) = split(all)
val com = cGen.from(cFields)
val tr = trGen.from(tFields)
A.Wrapper(com, tr)
}
}
def construct[T <: TheTrait] = new Constructor[T]
}
import Test.construct
def main(args: Array[String]): Unit = {
println(construct[Obj](5.1, false, "sub", 10))
}
}
I have (what I think is) an interesting problem trying to generically map sealed trait instances to and from a single Slick table. I have something like the following models:
sealed trait Base {
def baseField1: String
def baseField2: String // these types aren't important (I don't think?)
def id: String
}
case class SubA(
override val baseField1: String,
override val baseField2: String,
subAField: Int,
override val id: String
) extends Base
case class SubB(
override val baseField1: String,
override val baseField2: String,
subBField1: Boolean,
subBField2: Double,
override val id: String
) extends Base
// these used for the slick table defn
sealed trait ModelType
case object ModelA extends ModelType
case object ModelB extends ModelType
Now to make things simpler on the DB side, I've decided to create a table that has columns for the base fields, and then serializes the rest of the subclass-specific fields into a json object and persists that:
class BaseModels(tag: Tag) extends Table[BaseModel](tag, "base_models") {
override def id = column[String]("id", O.PrimaryKey)
def baseField1 = column[String]("base_field_1")
def baseField2= column[String]("base_field_2")
def modelType = column[ModelType]("type")
def details = column[JsValue]("details") // powered by pg_slick
override def * = (baseField1, baseField2, modelType, details, id) <> (
(construct _).tupled,
deconstruct,
)
}
lazy val baseModelsQuery = TableQuery[BaseModels]
Finally, I have manually created construct and deconstruct methods that read the model type and extract the proper fields from the JsValue column, or set the model type and create the JsValue based on the specific fields of the subclass being stored. It works, but it is a tedious solution and a pain to maintain when fields change anywhere. Having recently started looking in to Shapeless, I thought maybe I could try to use that to create generic construct and deconstruct functions instead, but am having a hell of a time doing that.
The first thing I wanted to accomplish was to create an HList of the fields stored in the JsObject using labelled generic to extract the desired fields, and came up with the following non-working idea:
// this will take a JsValue and extract the values into an HList O according to the input type I
trait FromJs[I, O <: HList] {
def from(js: JsValue): O
}
object FromJs {
// get the appropriate instance
def apply[I, O <: HList](implicit fjsa: FromJs[I, O]): FromJs[I, O] = fjsa
def create[I, O <: HList](j: JsValue => O): FromJs[I, O] = (js: JsValue) => j(js)
// the HNil instance I believe should be trivial
implicit val hNilFromJs = FromJs.create[HNil, HNil](_ => HNil)
// the idea here is that if we have an HList of labelled fields, we can access the JsObject with the field names
// and get the values that were stored in it
implicit def hListFromJs[
K <: Symbol, // the label type
H, // the type of the value stored in the JsObject we are trying to extract
IT <: HList, // the tail of the labelled generic hlist
I <: FieldType[K, H] :: IT, // the input labelled generic hlist type
T <: HList, // the tail of the values HList we are creating
](implicit
witness: Witness.Aux[K],
tFromJs: FromJs[IT, T], // we can create the HList of type T from the the rest of the labelled generic list IT
readerH: JsonReader[H], // needed to convert JsValue to H
): FromJs[I, H :: T] = { // creates a FromJs that can produce an H :: T given labelled generics I
val fieldName = witness.value.name
FromJs.create[I, H :: T] { js =>
js match {
case JsObject(fields) =>
fields(fieldName).convertTo[H] :: tFromJs.from(js)
}
}
}
}
Then I attempted to implement the generic construct function. The idea is to take the tuple from the *-projection and convert it to the HList needed by the specific output type. The common base fields are already provided by the tuple, I want to use FromJs to get the rest of the fields of the specific subclass, and then smoosh those together to create the generic rep of the subclass and use Generic to construct the actual base class instance. It seems like I need to create a lot of intermediate HLists to get a FromJs that only operates on the set of fields specific to the particular subclass. At this point I'm not even attempting to match on the ModelType, I'm just trying to get it to work for a single subclass, but don't seem to be able to. Here's the implementation so far:
class ConstructHelper[Out] {
def apply[
K <: Symbol, // the label type used by labelled generic
FRep <: HList, // common base trait fields
FLRep <: HList, // base trait fields with labels
JS <: JsValue, // the specific type of JsValue, not sure why this is needed but it pleased the compiler
In, // input tuple from DB
InRep <: HList, // input tuple fields from DB
SRep <: HList, // list of fields specific to output message type
SLRep <: HList, // those specific fields with their labels
OutA <: HList, // intermediate hlists to use with prepend
OutLA <: HList, // intermediate hlists to use with prepend for labelled fields
Out <: BaseModel, // desired output message type
ORep <: HList, // output fields
OLRep <: HList, // output fields with labels
FN <: Nat,
SN <: Nat, // these sizes seemed to help the compiler
](
in: In, // this will be the tuple from the *-projection of the slick table defn
)(implicit
baseGen: Generic.Aux[In, InRep], // Generic for the input *-projection tuple
outGen: Generic.Aux[Out, ORep], // Generic for the output subclass
specificsLGen: LabelledGeneric.Aux[Out, OLRep], // labelled generic for the output subclass. we will need to get the right section of this for use with `FromJs`
prependIn: Prepend.Aux[FRep, ModelType :: JS :: String :: HNil, InRep], // split up the *-projection tuple
lengthF: Length.Aux[FRep, FN], // these sizes I believe help the compiler determine how long of HList sections to extract
lengthFL: Length.Aux[FLRep, FN], // same thing, for labelled generics
prependOutA: Prepend.Aux[SRep, String :: HNil, OutA], // the tail of the output HList is made up of it's specific fields and the Id
prependOut: Prepend.Aux[FRep, OutA, ORep], // the output HList is made up of the common fields and the tail created above
lengthS: Length.Aux[SRep, SN], // the length of the specific fields needs to match ...
lengthSL: Length.Aux[SLRep, SN], // ... the length of the specific labelled fields
prependOutL: Prepend.Aux[FLRep, OutLA, OLRep], // the labelled fields of the output is the common labelled fields and the labelled fields tail
prependOutLA: Prepend.Aux[SLRep, FieldType[K, String] :: HNil, OutLA], // the labelled fields tail is the labelled specific fields and the labelled Id field
fjs: FromJs[SLRep, SRep], // we can take the specific labelled fields and get a HList of the specific field values
): Out = {
// hlist of input fields
val inHlist = baseGen.to(in)
val o = inHlist match {
// extract the common fields, jsvalue, and id
case (c: FRep) :: ModelType :: (jso: JsValue) :: (id: String) :: HNil =>
// use the fjs instance to convert the jsvalue to the specific fields, sew everything together
c ::: fjs.from(jso) ::: (id :: HNil)
}
// create the output instance
outGen.from(o)
}
}
Try to test it out:
val txt: SubA = construct[SubA]((
"base field 1",
"base field 2",
13,
"the_id"
))
I've run into a lot of various issues here, but at this iteration, the compiler can't seem to find instances of the implicit Prepends. I have a feeling that is only a symptom of some more general issue with how I'm going about this. Hopefully what I'm hoping to accomplish makes sense, more than happy to clarify if not. Is there any reason why this might not even be possible? Any ideas on accomplishing this, even if completely different than this strategy? Any and all help would be very much appreciated!
I've got some existing code along the lines of
trait Field[T]
object Fields {
case object Id extends Field[Int]
case object Name extends Field[String]
// ... and so on
}
// basically just a Map[Field[_], Any]
class QueryResultData {
def apply[T](field: Field[T]): T
}
def query(fields: Set[Field]): QueryMonad[QueryResultData]
So for example if I want to query the Id and Name data, I need to do something like:
val idsAndNames = for {
results <- query(Set(Fields.Id, Fields.Name))
} yield {
val id = results(Fields.Id)
val name = results(Fields.Name)
(id, name)
}
Having to manually extract each field's result is tedious, especially when the query includes more fields. What I'd like to be able to do is:
val idsAndNames: QueryMonad[(Int, String)] = query(Fields.Id -> Fields.Name)
And have some kind of typeclass handle the val id = ... part and reconstruct the tuple for me, e.g.
def query[Fields <: HList, Tuple](fields: Fields)
(implicit extractor: Extractor[Fields, T])
: QueryMonad[T]
How can I implement the Extractor typeclass so that I don't have to manually extract results?
What I've Tried
I figured this was a job for Shapeless, as the query method is meant to work on any number of fields, and is expected to give me back an appropriate tuple.
I defined a FieldExtractor type:
class FieldExtractor[T](field: Field[T]) {
def apply(results: QueryResultData): T = results(field)
}
and a polymorphic function for Field to FieldExtractor:
object makeFieldExtractor extends (Field ~> FieldExtractor) {
def apply[T](field: Field[T]) = new FieldExtractor[T]
}
and for simplicity's sake I'll start by dealing with HLists instead of Tuples:
val someFields = Fields.Id :: Fields.Name :: Fields.OtherStuff :: HNil
I tried using my makeFieldExtractor to convert someFields into someFieldExtractors. This is where I started running into trouble.
val someFieldExtractors = someFields.map(makeFieldExtractor)
error: could not find implicit value for parameter mapper: shapeless.ops.hlist.Mapper[MakeFieldExtractor.type,shapeless.::[Fields.Id.type,shapeless.::[Fields.Name.type,shapeless.::[Fields.OtherStuff.type,shapeless.HNil]]]]
It seems like the problem is that it's seeing types like Fields.Id.type when it probably should be seeing Field[Int]. If I explicitly specify the field types for someFields, the map works, but I don't want client code to have to do that. The compiler should do that for me. And let's assume I can't just change the Id/Name definitions to a val instead of a case object.
I found https://github.com/milessabin/shapeless/blob/master/examples/src/main/scala/shapeless/examples/klist.scala but didn't manage to make any successful use of it.
Here is how I would done it.
import shapeless.{::, HList, HNil}
import Field._
trait Field[A]
object Field {
case object IntField extends Field[Int]
case object StringField extends Field[String]
// Here is a little trick to proof that for any T that
// happened to be a subclass of Field[A] the Out is A
implicit def fieldExtractor[T, A]
(implicit ev: T <:< Field[A]): Extractor.Aux[T, A] =
new Extractor[T] {
override type Out = A
}
}
// The extractor for A
trait Extractor[A] {
type Out // Produces result of type Out
}
object Extractor {
// The Aux pattern http://gigiigig.github.io/posts/2015/09/13/aux-pattern.html
type Aux[A, Out0] = Extractor[A] {
type Out = Out0
}
// Proof that Out for HNil is HNil
implicit val hnilExtractor: Aux[HNil, HNil] =
new Extractor[HNil] {
override type Out = HNil
}
// Proof that Out for T :: H is hlist of extractor result for H and T
implicit def hconsExtractor[H, HO, T <: HList, TO <: HList]
(implicit H: Aux[H, HO], T: Aux[T, TO]): Aux[H :: T, HO :: TO] =
new Extractor[H :: T] {
override type Out = HO :: TO
}
}
type QueryMonad[A] = A
// Use dependent type Out as a result
def query[Fields](fields: Fields)(implicit extractor: Extractor[Fields]): QueryMonad[extractor.Out] = ???
val result = query(IntField :: StringField :: HNil)
I think I need a HList that is constrained to have all of its elements being a subtype of a certain type. LUBConstraint seems to be what I want, and indeed it does constrain the construction of such a HList - but I can't see how to get the evidence out again, so that I can map (actually, traverse, because it needs to be monadic) over the HList and call a method (that exists in the LUB type) on each of the elements.
In addition, I want the type of the HList resulting from the traverse operation to be exactly the same type as the type of the input HList.
The use case is a kind of functional "listener list" - all of the elements of the HList are "listeners" which must be notified of "events", accept or reject them, and return new versions of themselves with updated "internal state". If that was all I needed, then I could just use an ordinary immutable Scala collection. But I also want direct typed access to individual elements without using asInstanceOf - hence the motivation for trying to use HList.
In general if you have some operation that you want to perform on all of the elements in an HList, you'll want to map a polymorphic function value over the HList. For example, suppose I have the following setup:
trait Listener[L <: Listener[L]] {
def handle(s: String): Option[L]
}
class FooListener extends Listener[FooListener] {
def handle(s: String) =
if (s.size == 3) Some(this) else None
}
class BarListener extends Listener[BarListener ]{
def handle(s: String) = Some(this)
}
import shapeless._
val listeners = new FooListener :: new BarListener :: HNil
Now I want to send a String to each of these listeners and gather the results. If I just wanted to send a fixed value, this would be easy:
object event123 extends Poly1 {
implicit def listener[L <: Listener[L]] = at[L](_.handle("123"))
}
val result = listeners.map(event123)
Which will be appropriately typed as an Option[FooListener] :: Option[BarListener] :: HNil. If I'm using shapeless-contrib, I can sequence this HList:
import scalaz._, Scalaz._, shapeless.contrib.scalaz._
val sequenced: Option[FooListener :: BarListener :: HNil] = sequence(result)
Or just use traverse:
traverse(listeners)(event123)
Unfortunately there are restrictions on how polymorphic function values can be defined that mean that partial application isn't convenient, so if we don't know the String we're sending at compile time, this is a lot more complicated:
object event extends Poly1 {
implicit def listener[L <: Listener[L]] = at[(L, String)] {
case (listener, string) => listener.handle(string)
}
}
traverse(listeners.zip(listeners.mapConst("123")))(event)
Where we've zipped the elements with the string and then mapped a polymorphic function that takes tuples over the result. There are other ways you could do this using more or less the same approach, but none of them are terribly clear.
A completely different approach is just to skip the polymorphic function values and define a new type class:
trait Notifiable[L <: HList] {
def tell(s: String)(l: L): Option[L]
}
object Notifiable {
implicit val hnilNotifiable: Notifiable[HNil] = new Notifiable[HNil] {
def tell(s: String)(l: HNil) = Some(HNil)
}
implicit def hconsNotifiable[H <: Listener[H], T <: HList](implicit
tn: Notifiable[T]
): Notifiable[H :: T] = new Notifiable[H :: T] {
def tell(s: String)(l: H :: T) = for {
h <- l.head.handle(s)
t <- tn.tell(s)(l.tail)
} yield h :: t
}
}
def tell[L <: HList: Notifiable](s: String)(l: L) =
implicitly[Notifiable[L]].tell(s)(l)
And then:
val sequenced: Option[FooListener :: BarListener :: HNil] =
tell("123")(listeners)
This is less generic (it only works on Option, not arbitrary applicatives), but it doesn't require an extra dependency for sequencing, and it's arguably a little less muddled than jumping through hoops to partially apply a polymorphic function value because of weird limitations of the compiler.
I have done a few implementations of HList now. One based on Daniel Spiewak's High Wizardry in the Land of Scala talk and another based on a post in Apocalisp blog. The goal was to have a heterogenous list of which is not heterogenous in the primary type but rather the higher kind. For example:
val requests = Request[String] :: Request[Int] :: HNil
I would be able to do a map across the list to perform the request and result in a heterogenous list of the higher kind. So:
requests.map(execute)
should equal
String :: Int :: HNil
Sadly all my attempts have resulted in an HList of Any. Here is the code from a recent attempt:
class Request[+Out](o:Out) {
type O = Out
def v:O = o
}
object HList {
trait Func[-Elem,Out] {
type Apply[E <: Elem] <: Out
def apply[N <: Elem](e:N):Apply[N]
}
sealed trait HList[Base] {
type Head <: Base
type Tail <: HList[Base]
type Map[Out,F <: Func[Base,Out]] <: HList[Out]
def head:Head
def tail:Tail
def ::[A <: Base](a:A):HList[Base]
def map[Out,F <: Func[Base,Out]](f:F):Map[Out,F]
}
case class HNil[Base]() extends HList[Base] {
type Head = Nothing
type Tail = Nothing
type Map[Out,F <: Func[Base,Out]] = HNil[Out]
def head = error("Head of an empty HList")
def tail = error("Head of an empty HList")
def ::[A <: Base](a:A) = HCons(a,this)
def map[Out,F <: Func[Base,Out]](f:F) = new HNil[Out]
}
case class HCons[Base,A <: Base,B <: HList[Base]](head: A, tail: B) extends HList[Base] {
type Head = A
type Tail = B
type Map[Out,F <: Func[Base,Out]] = HCons[Out,F#Apply[Head],Tail#Map[Out,F]]
def ::[C <: Base](c:C) = HCons(c,this)
def map[Out,F <: Func[Base,Out]](f:F) =
HCons(f(head),tail.map(f))
}
val :: = HCons
}
object Test extends Application {
import HList._
val HNil = new HNil[Request[_]]
val list = new Request[Int](1) :: new Request[String]("1") :: HNil
val (a :: b :: HNil) = list
val y:Request[String] = b
val results = list.map[Any,Unwrap.type](Unwrap)
val i:Int = results.head
}
import HList._
object Unwrap extends Func[Request[Any],Any] {
type Apply[I <: Request[Any]] = I#O
def apply[N <: Request[Any]](e:N) = null.asInstanceOf[Apply[N]]
}
The other attempt was based on the Apocalisp version which uses fold to create a new HList and again it resulted in a HList of Any types. Any tips would be appreciated.
The HList implementation in shapeless is rich enough to subsume both HList and KList functionality. It provides a map operation which applies a higher-ranked function, possibly with type-specific cases, across it's elements yielding an appropriately typed HList result,
import shapeless.Poly._
import shapeless.HList._
// Define a higher-ranked function from Sets to Options
object choose extends (Set ~> Option) {
def default[T](s : Set[T]) = s.headOption
}
// An HList of Sets
val sets = Set(1) :: Set("foo") :: HNil
// Map our choose function across it ...
val opts = sets map choose
// The resulting value
opts == Option(1) :: Option("foo") :: HNil
Note that although it's the case in the above example there's no requirement that the HList elements share a common outer type constructor, it just has to be the case that the higher-ranked function mapped with has cases for all of the types involved,
// size is a higher-ranked function from values of arbitrary type to a 'size'
// which is defined as 1 by default but which has type specific cases for
// Strings and tuples
object size extends (Id ~> Const[Int]#λ) {
def default[T](t : T) = 1
}
implicit def sizeString = size.λ[String](s => s.length)
implicit def sizeTuple[T, U](implicit st : size.λ[T], su : size.λ[U]) =
size.λ[(T, U)](t => 1+size(t._1)+size(t._2))
size(23) == 1 // Default
size("foo") == 3 // Type specific case for Strings
size((23, "foo")) == 5 // Type specific case for tuples
Now let's map this across an HList,
val l = 23 :: true :: "foo" :: ("bar", "wibble") :: HNil
val ls = l map size
ls == 1 :: 1 :: 3 :: 10 :: HNil
In this case the result type of the function being mapped is constant: it's an Int no matter what the argument type is. Consequently the resulting HList has elements all of the same type, which means that it can usefully be converted to a vanilla list,
ls.toList == List(1, 1, 3, 10)
what you need is a Klist with type constructor Request, and a natural transformation execute: Request ~> Id. All of this is detailed in the marvelous type-level programming series of posts at Apocalisp, in particular:
Natural transformation literals
Klist basics
you can checkout the code for the whole series from Mark Harrah's up repo
In your case, you'll need something like
val reqList = new Request[Int](1) :^: new Request[String]("1") :^: KNil
val exec = new (Request ~> Id) { def apply[T](reqs: Request[T]): T = reqs.execute }
val results = reqList down exec
the down method above is conceptually the same as map for a nat transf M ~> Id; you also have more general map which from a nat transf M ~> N and a Klist of kind M yields a KList of kind N.
Note that you have an example of Map with HList in the recent (October 2016, 5 years after the OP) article "Using shapeless' HLists for extra type safety (in Akka Streams)" from Mikołaj Koziarkiewicz.
//glue for the ParserStageDefs
specs.map(s => Flow[Data].map(s.parser).map(s.processor))
.foreach(broadcast ~> _ ~> merge)
The problem lies in the fact that the type information in our specs list is not preserved. Or rather, not preserved the way we want to - the type of the List elements is ParserStageDef[_ >: Int with String], so the lowest common supertype for our decorator and incrementer.
The above implies that, when mapping between the parser and processor elements, the compiler has no way to provide the actual type T that's used within the given spec.
A solution
Here's where HLists come to the rescue. Because they preserve the complete type information for each element, it's possible to define our flow very similarly to our last attempt.
First, let's replace our list with an HList:
import shapeless.ops.hlist._
import shapeless._
//...
val specs = decorator :: incrementer :: HNil
val specsSize = specs.length.toInt
Now, for the mapping from ParserStageDefs into Flows, we need to take a different approach, as the map for HList requires something called P**oly - a polymorphic function value**.
Here's how one would look like in our case:
import shapeless.PolyDefns.~>
object toFlow extends (ParserStageDef ~> ProcessingFlow) {
override def apply[T](f: ParserStageDef[T]) =
Flow[Data].map(f.parser).map(f.processor)
}
For it to work, we'll also have change ProcessingFlow to type ProcessingFlow[_] = Flow[Data, Data, _], since the polymorphic function above expects a higher-kinded type.
Now, our central statement turns out to be:
//we convert to a List[ProcessingFlow[_]] for simplicity
specs.map(toFlow).toList.foreach(broadcast ~> _ ~> merge)
and we're all set!