【问题标题】:How can I use Shapeless to create a function abstracting over arity我如何使用 Shapeless 创建一个抽象于 arity 的函数
【发布时间】:2014-08-05 22:10:05
【问题描述】:

让我们考虑一个具体的例子。我有很多函数接受可变数量的参数,并返回一个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)

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

【问题讨论】:

    标签: scala shapeless


    【解决方案1】:

    可以使用 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]
    

    javaNonNeg 是一个Function2,所以在Java 中你可以使用javaNonNeg.apply(1, 4)

    【讨论】:

    • 这正是我想要的。谢谢!顺便说一句,确实知道为什么需要显式调用 ev() 吗?既然有从SSeq[R] 的“隐式转换”,为什么我不能只写ffp(l =&gt; (ftp(f)(l)).asJava)?为什么这会产生错误value asJava is not a member of type parameter S
    【解决方案2】:

    对于 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 _)  
    

    当然它也有一些缺点,但总的来说它可以满足您的需求。

    【讨论】:

    • 非常好,可行,但需要相当多的“样板文件”(您在第一个代码块中拥有的所有代码)。在标准库或其他“主流”库(如 Shapeless)中难道没有类似的东西吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-03-31
    • 2019-12-05
    • 2017-04-10
    • 1970-01-01
    • 2023-04-04
    相关资源
    最近更新 更多