Jeff Atwood of http://codinghorror.com argues in a recent article titled Monkeypatching for humans that the feature extensions methods in C# 3.0 allows for monkey patching in a controlled fashion, but is better left alone.
With the addition of the modifier this to parameters to a function, the function will be treated as a method on the class:
public static string Left(this string s, int len)
{
if (len == 0 || s.Length == 0)
return "";
else if (s.Length <= len)
return s;
else
return s.Substring(0, len);
}
var s = "Foobar"
s = s.Left(5);
This essentially makes Left a generic function, with a method defined for the class string.
The following code snippet displays the equivalent behaviour using the Common Lisp Object System (CLOS), but defined at a higher level (defgeneric), and with a method specialized on string objects:
(defgeneric left (sequence length)
(:documentation
"Return length items of sequence, starting from left,
or the original sequence if length is longer than the sequence.
Zero length yields the empty sequence."))
(defmethod left ((sequence string) length)
(cond
((= length 0) "")
((>= length (length sequence)) sequence)
(t (subseq sequence 0 length))))
This introduces the generic function left that can be applied to any sequence (note that if you actually do apply it to something that's not a string, you'll get a no-applicable-methods error, because the only method defined on the generic function is specialized on strings). Use like this:
CL-USER(1)> (left "foobar" 3) "foo" CL-USER(1)> (left "foobar" 42) "foobar"
To make it a bit more interesting, I'll show another (albeit slightly contrived) way to specialize on arguments:
(defmethod left ((sequence string) (length (eql 0)))
"")
(defmethod left ((sequence string) length)
(cond
((>= length (length sequence)) sequence)
(t (subseq sequence 0 length))))
Note that you can only do EQL-specialization on identity, which is evaluated at definition time. Now that we have moved out the string-specific parts, it's silly to only be able to use left on strings. So let's introduce a method that works on all sequences, and specialize on the empty sequence instead:
(defmethod left ((sequence string) (length (eql 0)))
"")
(defmethod left (sequence (length (eql 0)))
nil)
(defmethod left (sequence length)
(cond
((>= length (length sequence)) sequence)
(t (subseq sequence 0 length))))
CL-USER(1)> (left "" 3)
""
CL-USER(1)> (left '() 3)
NIL
CL-USER(1)> (left "foobar" 3)
"foobar"
CL-USER(1)> (left '(foo bar baz quux) 3)
(FOO BAR BAZ)
There has been work towards multimethods (or multiple dispatch as the concept is also called) in other languages, such as Python1, Perl2, Java3, and many others. The Wikipedia entry on Multiple Dispatch has code snippets for a few languages, for easy comparison.
So, in conclusion, this feature that languages of today are slowly adopting is what has been in Common Lisp with CLOS since the language was ANSI-standardized in 1994. It is a powerful construction that enables the programmer to extend any code, by making methods independent of classes.
| [1] | http://www.ibm.com/developerworks/linux/library/l-pydisp.html |
| [2] | http://search.cpan.org/src/DCONWAY/Class-Multimethods-1.70/tutorial.html |
| [3] | http://www.objectfaq.com/oofaq2/DynamicDispatch.htm |