Question

I'm new to Scala, and being able to pass functions to other functions is pretty neat-- but can I pass an arbitrary function reference to another function? The arity of said functional parameter will be fixed (that said, I'm also curious about whether you can pass a function with arbitrary arity as well). I keep getting tripped up on type errors. I've tried using Any but it doesn't seem to help.

E.g., I have the code below:

class CodeRunner(val user_defined: (Int) => Unit) {
  def run(input: Int) = {
    user_defined(input)
  }
}

def arbitrary_code(input: Int) = { println("Running with input " + input) }

val d1 = new CodeRunner(arbitrary_code)

d1.run(4)

And I get:

Running with input 4

Now, let's say that I want to pass the following function instead:

def arbitrary_code(input: String) = { println("Running with input " + input) }

How can I change my CodeRunner class to handle both?

Was it helpful?

Solution

Generic types allow you to define a class with a placeholder type that gets specified when an object gets instantiated. The compiler is happy because it can make sure that everything is type safe, and you're happy because you can instantiate the object and pass in arbitrary types for the value.

To use a generic type with your class, you could modify it like this:

class CodeRunner[T] (val user_defined: (T) => Unit) {
  def run(input: T) = {
    user_defined(input)
  }
}

The [T] after "class CodeRunner" is the important part -- it defines that there is a generic type T (you could replace T with another capital letter, etc.) which will be used within the class definition.

So, if you define a method:

def arbitrary_code(input: String) = { println("Running with input " + input) }

and then pass it in:

val d1 = new CodeRunner(arbitrary_code)

... the compiler then says "aha, for this instance of CodeRunner the generic type T is a string". And if you invoke

d1.run("string")

the compiler will be happy, but won't let you pass in d1.run(4).

OTHER TIPS

How can I change my CodeRunner class to handle both?

You can make the arbitrary type a parameter of the class:

class CodeRunner[T](val user_defined: (T) => Unit) {
  def run(input: T) = {
    user_defined(input)
  }
}

def arbitrary_code(input: Int) = { println("Running with input " + input) }

val d1 = new CodeRunner(arbitrary_code)

d1.run(4)

def arbitrary_code2(input: String) = { println("Running with input " + input) }

val d2 = new CodeRunner(arbitrary_code2)

d2.run("hello")

Note that the type of d2 is CodeRunner[String] which is not assignable to d1 which is CodeRunner[Int].

To pass an arbitrary function you can certainly use generics :

def run[T,U](f: T => U) = println(f)

For arbitrary arity, it's impossible because a function of type T => U is instance of Function1[U,T] and a function of type (T,U) => V is an instance of Function2[T,U,V]. (Also, I couldn't find any useful use case). However, there is a smart concept called "currying". It consists in transforming a function that takes multiple arguments and return a value in a function that takes a single argument and returns another function. Here's an example :

def nonCurriedAdd(x: Int, y: Int) = x + y
// nonCurriedAdd(4,6)
def curriedAdd(x: Int) = (y: Int) => x + y
// we can use some syntax sugar
def curriedAdd(x: Int)(y: Int) = x + y
// curriedAdd(4)(6)

So, you can now do `d1.run(curriedAdd). You can also transform a non-curried function in a curried one by using the "curried" method :

d1.run(nonCurriedAdd.curried)

can I pass an arbitrary function reference to another function? The arity of said functional parameter will be fixed

As always, write down the type of the function you are developing, and all becomes clear.

Your word "arbitrary" suggests that the function arguments work at any type. That is, they are polymorphic functions (or generic functions, in some languages).

The following should translate fairly cleanly to Scala:

== The type of an "arbitrary" function of fixed arity
f :: a -> b -> c -> d

-- The type of a function that accepts such a
-- function as an argument, and does something with it:
g :: (a -> b -> c -> d) -> a -> b -> c -> d

-- And a function that implements something of that type
g f a b c = f a b c

You might be able to come up with a few other such higher-order functions, that take functions of fixed arity, but arbitrary (i.e. polymorphic) type, and operate on them.

Classic higher-order functions are e.g.

map :: (a -> b) -> [a] -> [b]

fold :: (a -> b -> b) -> b -> [a] -> b

And many, many others.

scala> object codeRunner {
     |    def run[InputType, OutputType](code: InputType => OutputType) = (input: InputType) => code(input)
     | }
defined module codeRunner

scala> def someCode(x: Int) {
     |    println("This code uses " + x)
     | }
someCode: (x: Int)Unit

scala> def otherCode(y: String) {
     |    println("This code uses " + y)
     | }
otherCode: (y: String)Unit

scala> codeRunner.run(someCode)(10)
This code uses 10

scala> codeRunner.run(otherCode)("hello")
This code uses "hello"
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top