This was a good excuse for me to look into Shapeless , which I always wanted to do at some point :)
$ git clone git@github.com :milessabin/shapeless.git ... $ cd shapeless
(1)
Shapeless provides some abstractions over arity, and especially a heterogeneous list HList ( HList ). An arbitrary arity function can be thought of as an FnHList (a function that takes an HList argument).
$ sbt shapeless-core/console scala> import shapeless._ import shapeless._ scala> def isFunction[A](fun: A)(implicit fnh: FnHLister[A]) {} isFunction: [A](fun: A)(implicit fnh: shapeless.FnHLister[A])Unit scala> isFunction(math.sqrt _) scala> isFunction(math.random _)
(2)
Now let me require the function to return a Double :
scala> def isFunReturningDouble[A](fun: A)(implicit fnh: FnHLister[A] { type Result = Double }) {} isFunReturningDouble: [A](fun: A)(implicit fnh: shapeless.FnHLister[A]{type Result = Double})Unit scala> isFunReturningDouble(math.sqrt _) scala> isFunReturningDouble(math.signum _) <console>:12: error: could not find implicit value for parameter fnh: shapeless.FnHLister[Int => Int]{type Result = Double} isFunReturningDouble(math.signum _) ^
(3)
A class of type LUBConstraint can attest to the top of the argument list:
scala> def isValidFun[A, B <: HList](fun: A)(implicit fnh: FnHLister[A] { type Result = Double; type Args = B }, lub: LUBConstraint[B, Double]) {} isValidFun: [A, B <: shapeless.HList](fun: A)(implicit fnh: shapeless.FnHLister[A]{type Result = Double; type Args = B}, implicit lub: shapeless.LUBConstraint[B,Double])Unit scala> isValidFun(math.random _) scala> isValidFun((i: Int) => i.toDouble) <console>:12: error: could not find implicit value for parameter lub: shapeless.LUBConstraint[B,Double] isValidFun((i: Int) => i.toDouble) ^
(4)
Now we still need to somehow extract the attribute. At the type level, this will be the Length that is provided for the HList . To get the runtime value, a different type of ToInt class is ToInt .
Here is the final function:
import shapeless._ def doubleFunArity[A, B <: HList, C <: Nat](fun: A)(implicit fnh: FnHLister[A] { type Result = Double; type Args = B }, lub: LUBConstraint[B, Double], len: Length[B] { type Out = C }, res: ToInt[C] ): Int = res()
Test:
scala> doubleFunArity(math.sqrt _) res15: Int = 1 scala> doubleFunArity(math.random _) res16: Int = 0 scala> val g: (Double, Double) => Double = math.max _ g: (Double, Double) => Double = <function2> scala> doubleFunArity(g) res17: Int = 2
Please note that, unfortunately, many math operations are overloaded and without a strong restriction like Scala will not give you the Double version automatically, but for some reason will use the Int version:
scala> math.max _ res18: (Int, Int) => Int = <function2>
So I need to touch math.max _: ((Double, Double) => Double) to make this work.
Not to say that this is the best way to do this in your particular case, but I think it was a fun study.