DISCLAIMER:
This post covers a universal programming language design flaw using both Python
and Ruby code examples to showcase the issue.
I really don't like to read docs when learning a language, especially a
"so-called" high level language. I prefer to learn the language by interactive
sessions and object introspection. Then, when i have exhausted all abilities to
intuit the solution, i will roll my eyes, maybe blubber an expletive, and then
reluctantly crack open a user manual.
However, learning a new language (be it by this method or by official docs) is
frustrating when languages have method congestion from a need to present their
users with both a method for "in-place-mutation" and method for
"mutation-of-a-copy"; both sharing an almost exact spelling!
Yes i know, naming conventions can help. And one widely used convention is to
use weak verbs for the "mutation of a copy" and strong verbs for "in-place
mutation", consider:
py> a.reverse -> mutate 'a'
py> a.reversed -> new Array
However you will sooner or later encounter a word that does not have a proper
"weak verb" variant to describe the copy-mutate action, consider:
rb> point3d.offset(vector3d) -> mutate 'point3d'
rb> point3d.offseted(vector3d) -> HUH?
The Ruby language attempted to save the programmer from the scourge of
obtaining a four year degree in linguistics just to create intuitive
identifiers "on-the-fly", and they tried to remove this ambiguity by employing
"post-fix-punctuation" of the exclamation mark as a visual cue for in-place
modification of the object:
rb> a = [1,2,3]
rb> a.reverse!()
[3,2,1]
rb> a
[3,2,1]
...think of the exclamation mark yelling out; "Hey, i will modify this object
so be careful dude!" On the other hand, a method that mutates a copy will have
the same identifier except /without/ the exclamation mark:
rb> a = [1,2,3]
rb> a.reverse()
[3,2,1]
rb> a
[1,2,3]
Now whilst this punctuation solves the ambiguity issue in a reasonable manner,
it does not solve the congestion issue because for /every/ method that returns
a copy of the object, another method will exist with an exclamation mark
post-fixed that signifies object mutation. I don't like this because when i
inspect the object i see redundant method names:
rb> mutators = a.methods.grep(/.*!/)
rb> copyers = a.methods.select{|x| mutators.include?(x+"!")}
rb> copyers+mutators.sort
rb> ["flatten", "transform", "collect", "sort", "map", "uniq", "offset",
"reverse", "compact", "reject", "normalize", "slice", "collect!", "compact!",
"flatten!", "map!", "normalize!", "offset!", "reject!", "reverse!", "slice!",
"sort!", "transform!", "uniq!"]
Now that's just a small subset of the member functions of the Array object! Can
you imagine the mental overload induced when the entire set of methods must be
rummaged through each and every time!!!
rb> a.methods.length
141
*look-of-disapproval*
============================================================
SOLUTION
============================================================
The solution is simple. Do not offer the "copy-mutate" methods and force all
mutation to happen in-place:
py> l = [1,2,3]
py> l.reverse
py> l
[3,2,1]
If the user wants a "mutated copy" he should explicitly create a new object and
then apply the correct mutator method:
py> a1 = [1,2,3]
py> a2 = list(a1).reverse()
py> a1
[1,2,3]
py> a2
[3,2,1]
--
http://mail.python.org/mailman/listinfo/python-list