Let's say I have a typeclass such as this one:
trait Select[A] {
def select(selector: String): Set[A]
}
The typeclass provides the functionality "given a selector string, gimme a set of A
objects". However, it quickly gets tiring when you need to do this multiple times:
val setOfGrapes = Select[Grape].select("red-seedless")
val setOfApples = Select[Apple].select("fuji-apple")
// and so on...
My program lets users input strings that contain multiple selectors, so this all happens at runtime.
In the interest of making this more terse, how could I use shapeless to write a function that can be used like this?
val setOfGrapes :: setOfApples :: HNil =
selectMultiple("red-seedless, fuji")(Select[Grape] :: Select[Apple] :: HNil)
The motivation is being able to quickly chain multiple Select
s together. In essence, I am going from Select[Grape] :: Select[Apple] :: HNil
to Set[Grape] :: Set[Apple] :: HNil
by running a method from the Select
.
Ignoring the string processing (handling commas, globs, and other things), how would I go about implementing this?
I've tried this:
object fun extends Poly1 {
implicit def selectCase[A] = at[Select[A]](_.select(""))
}
def mapFun[H <: HList](inputs: H) = inputs map fun
The compiler tells me, could not find implicit value for parameter mapper: shapeless.ops.hlist.Mapper[fun.type,H]
. I assume this is because it cannot guarantee that every element of the HList is a Select[A]
. How do I tell the compiler this?
I've tried specifying a shapeless.UnaryTCConstraint[H, Select]
like this:
def mapFun[H <: HList](inputs: H)(implicit ev: shapeless.UnaryTCConstraint[H, Select]) = inputs map fun
But I receive the same error.
A friend recommended I use Comapped
, Mapper
, and ~>
, so I tried this:
object mapper extends (Select ~> Set) {
override def apply[A](s: Select[A]): Set[A] = s.select(???)
}
def mapFun[II <: HList, OO <: HList](inputs: II)(
implicit ev: Comapped.Aux[II, Select, OO],
ev2: Mapper.Aux[mapper.type, II, OO]
): OO = input map fun
But the compiler could not find implicit value for parameter mapper: shapeless.ops.hlist.Mapper[ammonite.$sess.cmd8.fun.type,II]
.
You should basically just add the required implicit evidence to your mapFun
method:
object fun extends Poly1 {
implicit def selectCase[A] = at[Select[A]](_.select(""))
}
def mapFun[H <: HList](inputs: H)(implicit m: Mapper[fun.type, H]) = inputs map fun
scala> mapFun(Select[Apple] :: Select[Grape] :: Select[Apple] :: HNil)
val res1: scala.collection.immutable.Set[Apple] :: scala.collection.immutable.Set[Grape] :: scala.collection.immutable.Set[Apple] :: shapeless.HNil = Set(Apple@44ebbbe8) :: Set(Grape@76eae32c) :: Set(Apple@666f67b6) :: HNil
To get your preferred API you can introduce a "manually curried" function like this:
class SelectMultipleApply(selector: String) {
def apply[H <: HList](inputs: H)(implicit m: Mapper[fun.type, H]) = inputs map fun
object fun extends Poly1 {
implicit def selectCase[A] = at[Select[A]](_.select(selector))
}
}
def selectMultiple(selector: String) = new SelectMultipleApply(selector)
scala> selectMultiple("red-seedless, fuji")(Select[Grape] :: Select[Apple] :: HNil)
val res5: scala.collection.immutable.Set[Grape] :: scala.collection.immutable.Set[Apple] :: shapeless.HNil = Set(Grape@63c81efa) :: Set(Apple@29dbae38) :: HNil