Skip R.
Skip R.

Reputation: 459

How do I map over an HList where all of the elements are instances of a typeclass?

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 Selects 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].

Upvotes: 2

Views: 111

Answers (1)

Jasper-M
Jasper-M

Reputation: 15086

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

Upvotes: 2

Related Questions