August 13, 2008

Function Pointers

Scala treats functions declared without a parameter list differently from those with a parameter list. It seems that
def f : Int = 1

isn't a first-class function, but
def fn(): Int = 2

is a first-class function. You cannot assign f to a value or pass it as a parameter. You can pass fn as a parameter trivially, but you need a bit of syntax to assign fn to a value.


class F {
def f: Int = 1
def fn(): Int = 2
}

class G {
def g(func: () => Int) : Int = func()
def yup: Int = g((new F).fn)
def yay: Int = {
val fn = () => (new F).fn()
g(fn)
}
//def nope1: Int = g((new F).f)
//def nope2: Int = {
// val fn: () => Int = (new F).fn()
// g(fn)
//}
//def nope3: Int = {
// val fn = (new F).fn()
// g(fn)
//}
}


None of the nope functions will compile, as fn() is taken as an immediate that returns an Int, not as a function object. Even if you try to "cast" it to () => Int, as in nope2, the compiler still takes fn as an immediate. The yup function works without any fuss which, to this naif, appears at odds with nope2. Both seem to be "casts" of the same ilk. Mmm.

Robey came up with the syntax in the function yay this afternoon. I arrived at the same place via a different path, by emulating how anonymous functions can be assigned to values. In polite company, I might say this is "idiomatic."

So, to assign a previously defined parameterless function to a value, you must declare it as:

def fn(): Int = {...}


and assign it thus:

val function = () => fn()

3 comments:

jherber said...

paren/param less function f:

def f:Int = 1

may be assigned by:

def h:Int = f

and can be passed to:

def g(m:Int) = m
>g(f)
res : Int = 1

or

def h(k: =>Int) = k
>h(f)
res : Int = 1

not sure what you mean by "immediate", but, if you define f this way:

def f:Int = {println("run"); 1}

you can verify when function f is executed.

Johan Kullbom said...

You can also call g with both f and fn using underscore (to ensure that the functions are not called):

g((new F).f _)
g((new F).fn _)

See http://www.scala-lang.org/docu/files/ScalaByExample.pdf (5.2 Currying)

Robey said...

I mentioned this in person, too, but I think the convention here (or, at least, the emerging convention?) is to use paren-less methods when defining accessors, and to use the empty-parens form when it's meant to be used as a proper method. At least one tool (I think it's vscaladoc) has started enforcing this by explicitly labeling paren-less methods as "properties".

In that case, it makes sense that it's a little harder to get at the actual function object for paren-less methods.