All services offered by a module should be available through a uniform notation, which does not betray whether they are implemented through storage or through computation.
satisfies this principle:
In the expression
the notation doesn't reveal how the feature is implemented. It could be a read only attribute or a function. Thus, changing the implementation from computation to storage or vice versa won't impact the call from a client of ACCOUNTs.
also satisfies UAP, for both setting and getting values. You can only call methods on objects and thus any access to an object variable is actually a method call that is performing the setting/getting action whether it is simple storage or computation.
Setters are like:
journey_plan.title = "South West Journey_plan"
This is actually saying:
journey_plan.title=("South West Journey_plan") which is a call to the title=() method on the journey_plan object
Getters are like:
which is actually saying:
journey_plan.title(), a call to the method title() on the journey_plan object
was at least partly constructed around this principle. In Self, objects have 'slots' which can hold values (be variables) or code (be functions).
allows attributes to hold property descriptors that make function calls look like simple setters and getters. If it were important to make an attribute behave like a function in terms of call syntax, this could be done by putting a callable object in the attribute.
, UAP is not supported. But since variables are local to objects, you only have to worry about the difference within that object's methods, and nowhere else. By insisting that every variable access (even local ones) be through a getter/setter, UAP can be supported in Smalltalk. This is also extraordinarily helpful if the object in question is persistent, and therefore subject to various db synchronization/transaction issues.
, UAP is not natively supported (because instance variables and functions are syntactically distinct) but is naturally brought by every java programmer (because not having read only access to instance variable, you'll have to write getter functions anyway).
The Java convention (which I studiously avoid) breaks UAP by insisting that accessor methods include a get/set prefix. This means that a java slot named "balance" that computes it every time might be called "balance()" a cached version would be called "getBalance()". I think it's better to name the function "balance()" and let the compiler figure out (through method overloading) whether it's a getter or setter. This restores the UAP by convention.
- Or you can have the "java slot named "balance" that computes it every time" be called "getBalance()"... ;-) [Anon2]
@Anon1, sorry, but you're wrong. A method that logically
represents the operation of fetching the value of the balance should be always
, even if it actually computes it on the fly instead of accessing a cache or reading some variable. Instead, a method that logically
represents a set of operations and returns a value, (and that does not logically represent a read-variable operation,) such as computeBalance()
, should not have the get
prefix in the name. [Anon3]
Also found in SmalltalkLanguage, where one uses the at: message to retrieve elements from a container by key.
values and constants obey this principle. You can even obtain execution tokens for them for deferred execution.
There are issues with this, as computation of a value and retrieval of storage can have different semantics beyond implementation:
- Computation of a value can be more expensive. If determining the balance of a bank account requires summing up all the transactions recorded against the account (rather than just returning a pre-computed balance), this is important information for the client to know. (Container classes are a big source of similar problems - while it is nice that the syntax for retrieving an element from an array, a linked list, or a hash table can be made the same, many times it is required to know what container you are dealing with so appropriate algorithms can be chosen.)
- Sometimes queries can have side-effects. (Many people feel they should not). Some queries have physical but not logical side-effects (are LogicallyConst?)--such as caching the value of an expensive computation.
- When dealing with operations which change the state of an object; there are additional semantic differences between a "set function" and a state-change function (which may change multiple attributes of the object).
While these differences don't completely invalidate Meyer's advice (one could come up with different naming schemes for queries with semantic differences noticeable to the client while maintaining the overall same syntax), they do illustrate an important point. Behaviors which are noticeable to the client should either be discoverable by the client, or at least documented in some way.
It should also be noted that Meyer's advice only ought to apply to external clients - while a = y.get_foo() or a = y.get(foo) ought to be preferred over a = y.foo (using C/C++/Java syntax), inside the implementation of an object direct getting and setting need not be discouraged. (And at some point, you will need a language primitive for retrieving an element from a record.) Even inside an implementation, scattering direct get/set references makes subsequent variable refactoring much more tedious.
It should further be noted that CommonLisp
gives you both UniformAccessPrinciple
and specialized interfaces for different datastructures. All of the Lisp datatypes - lists, arrays, association lists, hash tables, vectors, et al - can be accessed with 'elt'. Additionally, they each have their own accessor functions which typically offer better performance than the generic 'elt'. Arrays have 'aref'. Simple vectors have 'svref'. Hash tables have 'gethash'. Strings have 'char'.
Thus, in cases where these subtle distinctions are significant to the client, they can use a type-specific access function. In other cases, they can use the uniform accessor.
Although the idea carries a nice idealism, one thing that bothers me about it is that it hides the cost of getting the result
. An example from my website is the difference between what would otherwise be Florida.voteCount and Florida.countVotes(). One may take 3 days to calculate and the other is just referencing a pre-calculated attribute. If we have to reference that information multiple times in a given algorithm, the calculated version would bankrupt the machine. There are also advantages to isolating what is data and behavior that help with DivideAndConquer
and multi-language info sharing, but I think we've had this debate before. I'll link it here when/if I encounter it again. -- top
- You certainly have a point. We can mitigate the problem you describe by adding a little extra idealism. Add requirements (e.g. big-O) for time, space, power, energy, and other computational costs to module service contracts for such things as countVotes(). Make the language and compiler enforce these contracts, along with any others it enforces (such as typing system contracts). This feature would be quite useful anyway for both runtime-generated code in real-time systems, compile-time optimizations, and constraint-based code generation.
- That's over-engineering for most cases, in my opinion.
Seems to be related to AccessOrientedProgramming
has a new article on this topic http://www.eiffel.com/general/monthly_column/2005/Sept_October.html
. Seems to talk about the simplicity of allowing setter methods and value set to be truly the same, and a new compromise.