问题
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?
回答1:
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)
.
回答2:
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.
来源:https://stackoverflow.com/questions/25149179/how-can-i-use-shapeless-to-create-a-function-abstracting-over-arity