Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I use Shapeless to create a function abstracting over arity

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?

like image 437
avernet Avatar asked Aug 05 '14 22:08

avernet


2 Answers

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

like image 178
Travis Brown Avatar answered Sep 28 '22 05:09

Travis Brown


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.

like image 43
Yuriy Avatar answered Sep 28 '22 05:09

Yuriy