Both can be demonstrated by something like:
def needFunction(f: () => String) = {
println("calling function")
println(f())
}
def paramLess: String = {
println("calculating value")
"foo"
}
I want to pass paramLess
into needFunction
. When will it be evaluates?
needFunction(() => paramLess) // (1)
// calling function
// calculating value
// foo
This is obvious - paramLess
will be evaluated inside needFunction
. But this?
needFunction(paramLess _) // (2)
(2)
is the same as (1)
but I needed to take a moment to think how it would behave and run it in REPL to be sure.
This would happen quite often, especially with by-name params - you want to make some API easy to use by the user so you take thunk: => A
as argument, but internally you are passing () => A
to know exactly when you are evaluating (passing one by-name into another by-name and so on hoping that nothing in between evaluates results in some very fragile code).
Functions and methods with parameter don't have readability issues - if you don't put ()
anywhere it is not evaluated. You can compose it, pass it, return it - but if there is no argument passed, there is no evaluation. Paramless methods evaluate every time you use their name, so they are tricky if they perform side-effects or some expensive computations - that however is avoided and discouraged, as you are suggested to add at least ()
parameter list to side-effecting method. At the same time, they kind of pretend to be values, which don't need arguments. Apparently, here community decided that if they want to pretend to be values, they should be used as values, with () => value
as the way of creating a function - makes sense if we will not use them for side-effects.
By-name params are a similar case plus when it comes to readability, but - from what I saw - they have a lot of corner cases resulting in bugs when combined with eta-expansion and they also quite often (almost always?) appear in the context of side effects
def inSomeContext(context: Arguments)(thunk: => SideEffects) = ...
// Try.apply
// IO.apply
// option.fold(noneByName)(some => ...)
// ...
This makes them even more dangerous than paramless methods (where side-effects are discouraged) so a warning is even more justified.
The bottom line is that it is mostly about readability, safety and avoiding surprises:
- use methods without parameters when they can be uses as
val
ues (initialization order might be one reason) without side effects and surprises (there are more warnings that warns against side-effects in paramless methods),
- and by-name params as something that is nice to use in your API, but that is internally immediately and manually converted to a function (even
() => A
kind of function) or value (val evaluated = thunk
) that you would pass on safely.
Warning against other usages of these help keep the code readable and without bad surprises.