Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can Scala allow free Type Parameters in arguments (are Scala Type Parameters first class citizens?)?

I have some Scala code that does something nifty with two different versions of a type-parameterized function. I have simplified this down a lot from my application but in the end my code full of calls of the form w(f[Int],f[Double]) where w() is my magic method. I would love to have a more magic method like z(f) = w(f[Int],f[Double]) - but I can't get any syntax like z(f[Z]:Z->Z) to work as it looks (to me) like function arguments can not have their own type parameters. Here is the problem as a Scala code snippet.

Any ideas? A macro could do it, but I don't think those are part of Scala.

object TypeExample {
  def main(args: Array[String]):Unit = {
    def f[X](x:X):X = x              // parameterize fn
    def v(f:Int=>Int):Unit = { }     // function that operates on an Int to Int function
    v(f)                             // applied, types correct
    v(f[Int])                        // appplied, types correct
    def w[Z](f:Z=>Z,g:Double=>Double):Unit = {} // function that operates on two functions
    w(f[Int],f[Double])              // works
// want something like this:  def z[Z](f[Z]:Z=>Z) = w(f[Int],f[Double])
// a type parameterized function that takes a single type-parameterized function as an  
// argument and then speicalizes the the argument-function to two different types,  
// i.e. a single-argument version of w() (or wrapper)
  }
}
like image 260
jmount Avatar asked Jul 16 '09 18:07

jmount


2 Answers

You can do it like this:

trait Forall {
  def f[Z] : Z=>Z
}

def z(u : Forall) = w(u.f[Int], u.f[Double])

Or using structural types:

def z(u : {def f[Z] : Z=>Z}) = w(u.f[Int], u.f[Double])

But this will be slower than the first version, since it uses reflection.

EDIT: This is how you use the second version:

scala> object f1 {def f[Z] : Z=>Z = x => x}
defined module f1

scala> def z(u : {def f[Z] : Z=>Z}) = (u.f[Int](0), u.f[Double](0.0))
z: (AnyRef{def f[Z]: (Z) => Z})(Int, Double)

scala> z(f1)
res0: (Int, Double) = (0,0.0)

For the first version add f1 extends Forall or simply

scala> z(new Forall{def f[Z] : Z=>Z = x => x})
like image 143
Alexey Romanov Avatar answered Oct 11 '22 12:10

Alexey Romanov


If you're curious, what you're talking about here is called "rank-k polymorphism." See wikipedia. In your case, k = 2. Some translating:

When you write

f[X](x : X) : X = ... 

then you're saying that f has type "forall X.X -> X"

What you want for z is type "(forall Z.Z -> Z) -> Unit". That extra pair of parenthesis is a big difference. In terms of the wikipedia article it puts the forall qualifier before 2 arrows instead of just 1. The type variable can't be instantiated just once and carried through, it potentially has to be instantiated to many different types. (Here "instantiation" doesn't mean object construction, it means assigning a type to a type variable for type checking).

As alexy_r's answer shows this is encodable in Scala using objects rather than straight function types, essentially using classes/traits as the parens. Although he seems to have left you hanging a bit in terms of plugging it into your original code, so here it is:

// this is your code

object TypeExample {
  def main(args: Array[String]):Unit = {
    def f[X](x:X):X = x              // parameterize fn
    def v(f:Int=>Int):Unit = { }     // function that operates on an Int to Int function
    v(f)                             // applied, types correct
    v(f[Int])                        // appplied, types correct
    def w[Z](f:Z=>Z,g:Double=>Double):Unit = {} // function that operates on two functions
    w(f[Int],f[Double])              // works

// This is new code

    trait ForAll {
      def g[X](x : X) : X
    }

    def z(forall : ForAll) = w(forall.g[Int], forall.g[Double])
    z(new ForAll{def g[X](x : X) = f(x)})
  }
}
like image 33
James Iry Avatar answered Oct 11 '22 12:10

James Iry