Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generic scala function whose input is a function of variable arity

Tags:

generics

scala

I want to define a function f that takes another function g. We require g to take take n Doubles (for some fixed n) and return a Double. The function call f(g) should return the specific value of n.

For example, f(Math.max) = 2 since Math.sin has type (Double, Double) => Double, and f(Math.sin) = 1 since Math.sin has type Double => Double.

How can I define f using Scala generics?

I've tried several forms without success. For example:

def f[A <: Product](g: Product => Double) = {...}

This doesn't work since we cannot extract the value of n at compile time, and cannot constrain the A to contain only Double values.

like image 408
tba Avatar asked Jun 19 '13 08:06

tba


People also ask

What is the meaning of => in Scala?

=> is syntactic sugar for creating instances of functions. Recall that every function in scala is an instance of a class. For example, the type Int => String , is equivalent to the type Function1[Int,String] i.e. a function that takes an argument of type Int and returns a String .

What is the type of a function in Scala?

Scala functions are first-class values. Scala functions are first-class values. You must mention the return type of parameters while defining the function and the return type of a function is optional. If you don't specify the return type of a function, the default return type is Unit.

How do I use generics in Scala?

To use a generic class, put the type in the square brackets in place of A . The instance stack can only take Ints. However, if the type argument had subtypes, those could be passed in: Scala 2.

What is a type parameter in Scala?

Language. Methods in Scala can be parameterized by type as well as by value. The syntax is similar to that of generic classes. Type parameters are enclosed in square brackets, while value parameters are enclosed in parentheses.


3 Answers

There is a pattern called Magnet Pattern, created by the Spray team. It does exectly what you want

like image 152
4lex1v Avatar answered Nov 08 '22 07:11

4lex1v


This was a good excuse for me to look into Shapeless, something I always wanted to do at some point :)

$ git clone [email protected]:milessabin/shapeless.git
...
$ cd shapeless

(1)

Shapeless provides some abstractions over arity, and especially the representation as heterogeneous list (HList). A function of arbitrary arity can be seen as FnHList (a function that takes an HList as 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's require that the function returns 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)

The LUBConstraint type class can witness the upper bound 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 extract the arity somehow. On the type level this would be Length which is provided for HList. To get a runtime value, another type class ToInt is needed.

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

Note that unfortunately many math operations are overloaded, and without strong type constraint, Scala will not give you the Double version automatically, but will use the Int version for some reason:

scala> math.max _
res18: (Int, Int) => Int = <function2>

So I need the indirection math.max _: ((Double, Double) => Double) to make this work.


Not saying that this is the best way to do it in your concrete case, but I think it was a fun exploration.

like image 37
0__ Avatar answered Nov 08 '22 05:11

0__


Probably the easiest solution is to use overloading as

def f(g: () => Double) = 0;
def f(g: (Double) => Double) = 1;
def f(g: (Double, Double) => Double) = 2;
def f(g: (Double, Double, Double) => Double) = 2;
// ...

println(f(Math.pow _));
println(f(Math.sin _));

(You can't check function argument/return types at run time due to type erasure, so I believe you can't create a fully generic function that would satisfy your requirements.)

like image 39
Petr Avatar answered Nov 08 '22 05:11

Petr