There's an old pair of sayings that floats around programming communities like these.
Closures are a poor man's objects, and objects are a poor man's closures.
The fact of the matter is that, in a sufficiently modern language like Kotlin (or like most languages that we use nowadays), objects and closures are pretty similar. You could replace every class in your Kotlin program with a mess of functions and mutable variables they close over. Likewise, you could replace every function in your program with an object that has an invoke
function. But the former would be a constant wrestling match with the type system, and the latter would be absurdly verbose.
So Kotlin lets us do both. Which should you use? The advantage of functions is that they're short and snappy and to-the-point. And, to a functional programmer at least, functions should generally be free of side-effects. Objects, on the other hand, are loud and verbose. That's a bad thing, in that it takes longer to read and comprehend when skimming the code. But it's also a good thing, because it stops you from hiding complexity.
So if your function is simple, use a function. If it's complicated or stateful, use a named object and document it like any public class. As a few examples, here's how I would handle some different situations.
- A function to add two numbers together is simple, side-effect-free, and referentially transparent. Use a function.
- A function to add a number to a local
val
is still very simple. It's a closure, but the val
is immutable, so the function's behavior is predictable. Using an object would be overkill, so make it a function.
- A function that keeps track of how many times it's been called and prints out that number each time has side effects and local state. While it could be written as a fancy closure around a
var
, it would be better to make this a real object, whose counter variable is a genuine instance variable, so that anyone reading the code can see at a glance what's happening.