如何使用Shapeless创建一个抽象的arity函数

时间:2014-08-05 22:10:05

标签: scala shapeless

让我们考虑一个具体的例子。我有很多函数采用可变数量的参数,并返回Seq[T]。说:

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

对于这些功能中的每一个,我需要创建一个" Java版本"该函数返回java.util.List[T]。我可以创建" Java版本"以上功能:

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

这有点冗长,因为参数列表重复两次。相反,我想创建一个更高级别的函数,它将nonNeg形式的函数作为参数(任何数量和类型的参数,返回Seq[T])并返回一个函数它采用相同的参数,但返回java.util.List[T]。假设该函数被称为makeJava,那么我就可以写:

def javaNonNeg = makeJava(nonNeg)

可以使用Shapeless能力abstracting over arity来编写makeJava吗?如果可以,怎么做,不是,为什么以及如何做到这一点?

2 个答案:

答案 0 :(得分:8)

可以使用Shapeless来避免样板 - 你只需要使用普通的旧eta扩展将原始方法转换为FunctionN,然后转换为采用单个HList参数的函数,然后返回到具有新结果类型的FunctionN

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)

然后:

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]

javaNonNegFunction2,因此从Java可以使用javaNonNeg.apply(1, 4)

答案 1 :(得分:3)

对于2个或更多(在下面的代码中为4个)参数,您可以使用隐式参数功能,通过输入参数类型解析结果类型

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)
}

并在def javaNonNeg = makeJava(nonNeg)函数看起来像:

object asJavaFunc extends Func((_ : Seq[Int]).asJava)  
import asJavaFunc._

def javaNonNeq = makeFunc(nonNeg _)  

当然它有一些缺点,但通常它可以满足您的需求。