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?
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)
.