R
Rick Johnson
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 interactivesessions 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 exclamationmark 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 methodsmust 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 objectand 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]
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 interactivesessions 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 exclamationmark 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 methodsmust 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 objectand 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]