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)
.
For 2 and more (in code below to 4) parameters you can use implicit parameters feature, for resolve result type by input parameter type
sealed trait FuncRes[F] {
type Param
type Result
def func : F => Param => Result
}
class Func[T, R](fn : T => R) {
trait FR[F, P] extends FuncRes[F] { type Param = P; type Result = R }
implicit def func2[T1,T2] = new FR[(T1,T2) => T, (T1,T2)] {
def func = f => p => fn(f.tupled(p))
}
implicit def func3[T1,T2,T3] = new FR[(T1,T2,T3) => T, (T1,T2,T3)] {
def func = f => p => fn(f.tupled(p))
}
implicit def func4[T1,T2,T3,T4] = new FR[(T1,T2,T3,T4) => T, (T1,T2,T3,T4)] {
def func = f => p => fn(f.tupled(p))
}
def makeFunc[F](f : F)(implicit ev : FuncRes[F]): ev.Param => ev.Result =
ev.func(f)
}
and after your def javaNonNeg = makeJava(nonNeg)
function will look like:
object asJavaFunc extends Func((_ : Seq[Int]).asJava)
import asJavaFunc._
def javaNonNeq = makeFunc(nonNeg _)
And of course it has some disadvantages, but generally it satisfy your needs.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With