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

Reply via email to