Each of these questions has been answered elsewhere on this site, but I don't think anything handles them all together. So:
Braces and equals
Methods defined with an equals sign return a value (whatever the last thing evaluates to). Methods defined with only braces return Unit
. If you use an equals but the last thing evalutes to Unit
, there is no difference. If it's a single statement after an equals sign, braces are not required; this makes no difference to bytecode. So 1., 2., and 5. are all essentially identical:
def f1(s: String) = { println(s) } // println returns `Unit`
def f2(s: String) { println(s) } // `Unit` return again
def f5(s: String) = println(s) // Don't need braces; there's only one statement
Functions vs. methods
A function, often written A => B
, is a subclass of one of the Function
classes, e.g. Function1[A,B]
. Because this class has an apply
method, which Scala magically calls when you just use parens without a method name, it looks like a method call--and it is, except it's a call on that Function
object! So if you write
def f3 = (s: String) => println(s)
then what you are saying is "f3
should create an instance of Function1[String,Unit]
which has an apply
method that looks like def apply(s: String) = println(s)
". So if you say f3("Hi")
, this is first calls f3
to create the function object, and then calls the apply
method.
It's rather wasteful to create the function object every single time you want to use it, so it makes more sense to store the function object in a var:
val f4 = (s: String) => println(s)
This holds one instance of the same function object that the def
(method) would return, so you don't have to recreate it each time.
When to use what
People differ on the convention of : Unit = ...
and { }
. Personally, I write all methods that return Unit
without an equals sign--this is an indication to me that the method is almost surely useless unless it has some sort of side-effect (mutates a variable, performs IO, etc.). Also, I generally only use braces when required either because there are multiple statements or because the single statement is so complex I want a visual aid to tell me where it ends.
Methods should be used whenever you want, well, a method. Function objects should be created any time you want to pass them into some other method to use them (or should be specified as parameters any time you want to be able to apply a function). For example, suppose you want to be able to scale a value:
class Scalable(d: Double) {
def scale(/* What goes here? */) = ...
}
You could supply a constant multiplier. Or you could supply something to add and something to multiply. But most flexibly, you'd just ask for an arbitrary function from Double
to Double
:
def scale(f: Double => Double) = f(d)
Now, maybe you have an idea of a default scale. That's probably no scaling at all. So you might want a function that takes a Double
and returns the very same Double
.
val unscaled = (d: Double) => d
We store the function in a val
because we don't want to keep creating it over and over again. Now we can use this function as a default argument:
class Scalable(d: Double) {
val unscaled = (d: Double) => d
def scale(f: Double => Double = unscaled) = f(d)
}
Now we can call both x.scale
and x.scale(_*2)
and x.scale(math.sqrt)
and they'll all work.