Nine ways to define a method in Scala?

So I've been trying to puzzle through the various ways you can define stuff in Scala, complicated by my lack of understanding of the way {} blocks are treated:

object NewMain extends Thing{

    def f1 = 10
    def f2 {10}
    def f3 = {10}
    def f4() = 10
    def f5() {10}
    def f6() = {10}
    def f7 = () => 10
    def f8 = () => {10}
    def f9 = {() => {10}}

    def main(args: Array[String]){
        println(f1)     // 10
        println(f2)     // ()
        println(f3)     // 10
        println(f4)     // 10
        println(f4())   // 10
        println(f5)     // ()
        println(f5())   // ()
        println(f6)     // 10
        println(f6())   // 10
        println(f7)     // <function0>
        println(f7())   // 10
        println(f8)     // <function0>
        println(f8())   // 10
        println(f9)     // <function0>
        println(f9())   // 10
    }

}

Presumably some of these are equivalent, some of these are syntactic sugar for others, and some are things I should not use, but I can't for the life of me figure it out. My specific questions are:

  • How is it that println(f2) and println(f5()) gives unit? Isn't the last item in the block 10? How is it different from println(f3()), which gives 10?

  • If println(f5) gives unit, shouldn't println(f5()) be invalid, since unit is not a function? The same applies to println(f6) and println(f6())

  • Of all the ones which print 10: f1, f3, f4, f4(), f6, f6(), f7(), f8(), f9(), is there any functional difference between them (in terms of what it does) or usage differences (in terms of when I should use which)? Or are they all equivalent?


Solution 1:

To answer your questions in order:

  • f2 and f5() return Unit because scala takes any def without an "=" to be a function that returns Unit, regardless of what the last item in a block is. This is a good thing, since otherwise it would not be rather verbose to define a function that does not return anything.
  • println(f5()) is valid, even though it returns Unit because in scala Unit is a valid object, though admittedly not one you can instantiate. Unit.toString() is a valid, if not generally useful, statement, for example.
  • Not all the versions that print out 10 are the same. Most importantly, f7,f8, and f9 are actually functions that return functions that return 10, rather than returning 10 directly. When you declare def f8 = () => {10}, you are declaring a function f8 that takes no arguments and returns a function that takes no arguments and returns a single integer. When you invoke println(f8) then f8 dilligently returns that function to you. When you call println(f8()) it returns the function, then immediately invokes it.
  • The functions f1,f3,f4, and f6 are all essentially equivalent in terms of what they do, they vary only in terms of style.

As "user unknown" indicates, the braces are only important for scoping purposes and do not make any difference in your use case here.

Solution 2:

def f() {...}

is sytactic sugar for

def f(): Unit = {...}

So if you omit the "=" the method will always return an object of type Unit. In Scala, methods and expressions always return something.

def f() = 10
is sytactic sugar for
def f() = {
10
}

If you write def f() = () => 10, it's the same as writing

def f() = {
() => 10
}

So that means f is returning a function object. You could however write

val f = () => 10

When you call that with f() it returns 10 Function objects and methods can be used interchangingly in most cases, but there are a few syntactic differences. e.g. When you write

def f() = 10
println(f)

you get "10", but when you write

val f = () => 10
println(f)

you get

<function0>

On the other hand when you have this

val list = List(1,2,3)
def inc(x: Int) = x+1
val inc2 = (x: Int) => x+1
println(list.map(inc))
println(list.map(inc2))

Both println will print the same thing

List(2,3,4)

When you use the name of a method at a place where a function object is expected and the method signature matches the signature of the expected function object it is automatically converted. So list.map(inc) gets automatically converted by the scala compiler into

list.map(x => inc(x))

Solution 3:

Six years later, in a future version of Scala to be released even further in the future, things have improved:

  • The definitions f2 and f5 have been removed as "procedure syntax"
  • The ability to call f4 f5 and f6 without parens has been removed.

That brings our 9 ways of defining a function and 15 ways of calling them down to 7 ways of defining a function and 10 ways of calling them:

object NewMain extends Thing{

    def f1 = 10
    def f3 = {10}
    def f4() = 10
    def f6() = {10}
    def f7 = () => 10
    def f8 = () => {10}
    def f9 = {() => {10}}

    def main(args: Array[String]){
        println(f1)     // 10
        println(f3)     // 10
        println(f4())   // 10
        println(f6())   // 10
        println(f7)     // <function0>
        println(f7())   // 10
        println(f8)     // <function0>
        println(f8())   // 10
        println(f9)     // <function0>
        println(f9())   // 10
    }
}

See also lampepfl/dotty2570 lampepfl/dotty#2571

As a result, it's relatively clear which syntax is optional (e.g. {}s) and which definitions are equivalent (e.g. def f4() = 10 and def f7 = () => 10). Hopefully, some day when Dotty/Scala-3.0 is released, newbies learning the language will no longer face the same confusion I did six years ago.

Solution 4:

def f1 = 10    
def f2 {10}    

The second form does not use an assignment. Therefore you can think of it as an Procedure. It is not meant to return something, and returns therefore Unit, even if the last statement could be used to return something specific (but it could be an if-statement, which would only have something specific in one branch).

def f1 = 10    
def f3 = {10}  

You don't need braces here. You need them, for instance, if you define a val, so the scope of this val is restricted to the enclosing block.

def sqrGtX (n:Int, x: Int) = {
  val sqr = n * n
  if (sqr > x) 
    sqr / 2 
  else x / 2 
}  

You need the curly braces to define val sqr here. If the val is declared in an inner branch, the curly braces don't need to be at the top-level of the method:

def foo (n:Int, x: Int) = 
  if (n > x) {
    val bar = x * x + n * n
    println (bar) 
    bar - 2  
  } else x - 2 

For further investigation when two methods return the same result, you can compile them and compare the bytecode. Two binary identical methods will be identic.