Search code examples
scalashapeless

How can I use Shapeless to create a function abstracting over arity


Let's consider a specific example. I have lots of functions that take a variable number of arguments, and return a Seq[T]. Say:

def nonNeg(start: Int, count: Int): Seq[Int] = 
    Iterator.from(start).take(count).toSeq

For each one of those function, I need to create a "Java version" of that function, returns a java.util.List[T]. I can create the "Java version" of the above function with:

def javaNonNeg(start: Int, count: Int): java.util.List[Int] =
    nonNeg(start, count).asJava

This is somewhat verbose, as the list of parameters is duplicated twice. Instead, I'd like to create a higher level function that takes as a parameter a function of the form of nonNeg (any number and type of arguments, returns a Seq[T]) and returns a function which takes the same arguments, but returns a java.util.List[T]. Assuming that function was called makeJava, I'd then be able to write:

def javaNonNeg = makeJava(nonNeg)

Can makeJava be written using Shapeless ability to abstracting over arity? If it can, how, and it not, why and how else can this be done?


Solution

  • It is possible to use Shapeless to avoid the boilerplate—you just need to turn the original method into a FunctionN using plain old eta expansion, then convert to a function taking a single HList argument, and then back to a FunctionN with the new result type:

    import java.util.{ List => JList }
    import shapeless._, ops.function._
    import scala.collection.JavaConverters._
    
    def makeJava[F, A, L, S, R](f: F)(implicit
      ftp: FnToProduct.Aux[F, L => S],
      ev: S <:< Seq[R],
      ffp: FnFromProduct[L => JList[R]]
    ) = ffp(l => ev(ftp(f)(l)).asJava)
    

    And then:

    scala> def nonNeg(start: Int, count: Int): Seq[Int] = 
         |     Iterator.from(start).take(count).toSeq
    nonNeg: (start: Int, count: Int)Seq[Int]
    
    scala> val javaNonNeg = makeJava(nonNeg _)
    javaNonNeg: (Int, Int) => java.util.List[Int] = <function2>
    
    scala> javaNonNeg(1, 4)
    res0: java.util.List[Int] = [1, 2, 3, 4]
    

    javaNonNeg is a Function2, so from Java you can use javaNonNeg.apply(1, 4).