Search code examples
scalafunctional-programmingshapeless

How to map a function over the HList of functions from a certain type to some type?


For simplicity sake let's assume my HList instance holds exactly 2 values:

def intToString(x: Int) = x + "_"
def intToDouble(x: Int) = x * 10d
val fns = (intToString _ :: intToDouble _ :: HNil)

Now I'd like, having some Hlist of ints, to be able to do this:

(fns zip (1 :: 2 :: HList) map {case (f, x) => f(x) }

to get

("1_", 10.0)

From now on, assume that I don't know what I am doing and I totally understand all my paltriness in the faces of the functional gods.

I already looked at shapeless' wiki and, as far as I understood, I should make a function from Int to T that can be accepted by shapeless. Did I understood correct? Here's what I got:

object mapApply extends Poly1 {
  implicit def default[T] = at[Function[Int,T]](f => f.apply _)
}

At this point I'm completely lost and don't even have a clue of how to proceed. But I'm kinda fascinated by the potential power and expressiveness that this could deliver, so I'd really like to understand how this stuff works. I would really appreciate if your answers will not be just code snippets but rather more open and explanatory.

P.S. SO's engine just suggested me a c++ tag. Did I say something C++-ish?


Solution

  • It looks like you're mixing method and function definition syntax—I'll assume you meant something like the following:

    val intToString = (x: Int) => x + "_"
    val intToDouble = (x: Int) => x * 10d
    val fns = intToString :: intToDouble :: HNil
    

    Now you can use zipApply, which just pairs functions and their arguments:

    val res = fns.zipApply(1 :: 2 :: HNil)
    

    If for some reason zipApply didn't exist, you could accomplish the same thing with a Poly1:

    object apply extends Poly1 {
      implicit def default[I, O]: Case.Aux[(I => O, I), O] =
        at[(I => O, I)] { case (f, x) => f(x) }
    }
    
    val res = fns.zip(1 :: 2 :: HNil).map(apply)
    

    Or this, if you don't want the extra genericity:

    object applyToInt extends Poly1 {
      implicit def default[T]: Case.Aux[(Int => T, Int), T] =
        at[(Int => T, Int)] { case (f, x) => f(x) }
    }
    

    So you weren't too far off—you just needed to have a case in your Poly1 for the pair of the function and the argument, not just the function.