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)
andprintln(f5())
givesunit
? Isn't the last item in the block10
? How is it different fromprintln(f3())
, which gives10
?If
println(f5)
givesunit
, shouldn'tprintln(f5())
be invalid, sinceunit
is not a function? The same applies toprintln(f6)
andprintln(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
andf5()
returnUnit
because scala takes anydef
without an "=
" to be a function that returnsUnit
, 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 returnsUnit
because in scalaUnit
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
, andf9
are actually functions that return functions that return10
, rather than returning10
directly. When you declaredef f8 = () => {10}
, you are declaring a functionf8
that takes no arguments and returns a function that takes no arguments and returns a single integer. When you invokeprintln(f8)
thenf8
dilligently returns that function to you. When you callprintln(f8())
it returns the function, then immediately invokes it. - The functions
f1
,f3
,f4
, andf6
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
andf5
have been removed as "procedure syntax" - The ability to call
f4
f5
andf6
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.