LangWart: Method congestion from mutate multiplicty

Rick Johnson rantingrickjohnson at
Sat Feb 9 02:50:44 CET 2013

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!()
rb> a

...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()
rb> a

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 ={|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



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

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
py> a2

More information about the Python-list mailing list