I’m going to do something different and come at this from a Java angle.
Item 16 of Effective Java: Third Edition (and it is present in older editions, though perhaps with a different item number) states:
In public classes, use accessor methods, not public fields
It goes on to describe the reasons as follows:
Because the data fields of such classes [with public fields] are accessed directly, these classes do not offer the benefits of encapsulation (Item 15). You can’t change the representation without changing the API […] and you can’t take auxiliary action when a field is accessed.
[I]f a class is accessible outside its package, provide accessor methods to preserve the flexibility to change the class’s internal representation. If a public class exposes its data fields, all hope of changing its representation is lost because client code can be distributed far and wide.
When following the recommendation of Item 16, there is also not a way just from the method structure to distinguish methods which merely read fields, and those which don’t. This is intentional - if you could distinguish them, you couldn’t swap one for the other, and you would lose the flexibility to evolve APIs. It is the purpose of documentation to explain what methods are expensive, IO-bound, etc.
The Uniform Access Principle is a direct encoding of Item 16 in Scala - val
s are actually encoded as private fields and public methods, such that using a val
is automatically following the above advice and retaining the ability to change the class’s internal representation.
Forbidding declaring nullary methods is essentially violating Item 16 in all cases. It prevents API evolution, which ends up being quite bad for widely-used libraries.
In general, IO-bound methods should be nilary (because they are side-effecting), which would clearly distinguish them from nullary methods that are pure or field reads.
Forbidding nullary methods severely hampers your ability to evolve APIs. Suppose you write a collection type (that doesn’t follow the scala.collection
hierarchy), and you expose its length as val length
. This will prevent you in the future from implementing a wrapper collection that forwards its length to the underlying collection. You can’t do that, because it needs to be a val
. Now you need to store an extra, redundant copy of the length in a field.
Suppose you have a mutable collection, and you keep track of its length in a mutable var
. Because you don’t want users of your class to be able to modify the length, the var
must be private and the value must be publicly exposed through a def
. Now your mutable and immutable collections have confusingly different APIs (and can’t share an ancestor) because the mutable one can’t be backed by a val
.
In short, there are very good reasons for the Uniform Access Principle. It is the extension of a principle even found in good Java development practices. If you want, you can probably use Scalafix to prevent the use of nullary methods (as mentioned by @Jasper-M), but you will likely find it hampering you in many, many ways.