mapping over HList inside a function
Asked Answered
T

1

7

The following code seems obvious enough to compile and run

case class Pair(a: String, b: Int)

val pairGen = Generic[Pair]

object size extends Poly1 {
  implicit def caseInt = at[Int](x => 1)
  implicit def caseString = at[String](_.length)
}

def funrun(p: Pair) = { 
  val hp: HList = pairGen.to(p)
  hp.map(size)
}

but the compiler says "could not find implicit value for parameter mapper". In my use case, I want to map over an HList to get and HList of String(s) and then convert the HList of String(s) into Scala List[String]. Any ideas?

Td answered 8/9, 2015 at 0:49 Comment(1)
If you remove the type of the hp, then the code will work as it is. So, instead of "val hp: HList = pairGen.to(p)" you should have "val hp = pairGen.to(p)"Cule
M
12

First we can create a Poly1 similar to size which we can use to map an HList to an HList of Strings.

object strings extends Poly1 {
  implicit def caseInt = at[Int](_.toString)
  implicit def caseString = at[String](identity)
}

You were already using Generic[Pair] to turn a Pair into an HList, but you couldn't map over your hp because there is no evidence in your funrun that you can map over it. We can solve this by using implicit parameters.

def funRun[L <: HList, M <: HList](
  p: Pair
)(implicit
  gen: Generic.Aux[Pair, L],
  mapper: Mapper.Aux[strings.type, L, M]
) = gen.to(p).map(strings)
  • Our first implicit parameter gen can turn a Pair into an HList of type L.
  • Our second implicit parameter mapper can use our strings polymorphic function to map an HList of type L to an HList of type M.

We can now use funRun to turn a Pair into an HList of Strings :

scala> funRun(Pair("abc", 12))
res1: shapeless.::[String,shapeless.::[String,shapeless.HNil]] = abc :: 12 :: HNil

But you wanted to return a List[String]. To turn our HList M (the result of mapping to String) to a List[String] we need a ToTraversable, so we add a third implicit parameter :

import shapeless._, ops.hlist._

def pairToStrings[L <: HList, M <: HList](
  p: Pair
)(implicit
  gen: Generic.Aux[Pair, L],
  mapper: Mapper.Aux[strings.type, L, M],
  trav: ToTraversable.Aux[M,List,String]
): List[String] = gen.to(p).map(strings).toList

Which we can use as :

scala> pairToStrings(Pair("abc", 12))
res2: List[String] = List(abc, 12)
Mistaken answered 8/9, 2015 at 9:34 Comment(7)
Thank you @Peter! This is exactly what I was looking for!Td
After I closed this thread I've been going in circles trying to understand why the following basic enhancement to composite type does not work: ` class A[T](val x: T); object g extends Poly1 { implicit def caseString = at[String] { identity } }; def toStr[L <: HList]( a: A[String] )(implicit gen: Generic.Aux[A[String], L], mapper: Mapper[g.type, L] ) = gen.to(a).map(g)`Td
I'm getting could not find implicit value for parameter mapper: shapeless.ops.hlist.Mapper[g.type,gen.Repr].Td
Have you imported shapeless.ops.hlist._ ?Mistaken
Importing shapeless.ops.hlist._ makes the above code work. Thank you @Peter! My use case has one additional twist - a variable parameter list. This code compiles REPL: ` case class A[T](a: T) case class As(as: A[String]*) object f extends Poly1 { implicit def caseA = at[A[String]](identity) } def toStr[L <: HList, M <: HList]( as: As )(implicit gen: Generic.Aux[As, L], mapper: Mapper.Aux[f.type, L, M] ) = gen.to(as).map(f) val a = A[String]("one") val as: As = As(a, a, a). BUT the call to toStr(as)` gives the mapper error as before.Td
@Td If you experiment in the REPL, you will see that the output type (gen.Repr) from Generic[As] is Seq[A[String]] :: HNil. Your g function only has a case for A[String] so the g function cannot be used to map over an HList of type gen.Repr.Mistaken
Thanks again for your help @Peter! Adding a case implicit def caseGenAs = at[Seq[A[String]]](_.map({case A(a) => a}).mkString(",")) into g solves the problem.Td

© 2022 - 2024 — McMap. All rights reserved.