Although I love strings, sometimes the String class can break your heart. For example, in C#, there is no String.Left() function. Fair enough; we can roll up our sleeves and write our own function lickety-split:
public static string Left(string s, int len)
{
if (len == 0 || s.Length == 0)
return "";
else if (s.Length <= len)
return s;
else
return s.Substring(0, len);
}
And call it like so:
var s = "Supercalifragilisticexpialidocious"; s = Left(s, 5);
Fairly painless, right?
But with the advent of C# 3.0, there's an even better way -- extension methods. With an extension method, we "extend" the String to add the missing function. The code is fairly similar; I'll highlight the changed parts in red.
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);
}
And now we can call it as if this very method existed on the String class as shipped:
var s = "Supercalifragilisticexpialidocious"; s = s.Left(5);
Pretty slick. It's difficult not to fall in love with extension methods, as they allow you to mold classes into exactly what you think they should be. This is fairly innocuous in C#, as extension methods only allow you to add new functionality to classes, not override, remove, or replace anything.
But imagine if you could.
Well, that's exactly how it is in other, more dynamic languages such as Javascript, Python, Perl, and Ruby. Something as prosaic as C# extensions is old hat to these folks. In those languages, you could redefine everything in the String class if you wanted to. This is commonly known in dynamic language circles as monkeypatching.
If the idea of monkeypatching scares you a little, it probably should. Can you imagine debugging code where the String class had subtly different behaviors from the String you've learned to use? Monkeypatching can be incredibly dangerous in the wrong hands, as Avdi Grimm notes:
Monkey patching is the new black [in the Ruby community]. It's what all the hip kids are doing. To the point that smart, experienced hackers reach for a monkey patch as their tool of first resort, even when a simpler, more traditional solution is possible.I don't believe this situation to be sustainable. Where I work, we are already seeing subtle, difficult-to-debug problems crop up as the result of monkey patching in plugins. Patches interact in unpredictable, combinatoric ways. And by their nature, bugs caused by monkey patches are more difficult to track down than those introduced by more traditional classes and methods. As just one example: on one project, it was a known caveat that we could not rely on class inheritable attributes as provided by ActiveSupport. No one knew why. Every Model we wrote had to use awkward workarounds. Eventually we tracked it down in a plugin that generated admin consoles. It was overwriting
Class.inherited(). It took us months to find this out.This is just going to get worse if we don't do something about it. And the "something" is going to have to be a cultural shift, not a technical fix. I believe it is time for experienced Ruby programmers to wean ourselves off of monkey patching, and start demonstrating more robust techniques.
Try to imagine a world where every programmer you know is a wannabe language designer, bent on molding the language to their whims. When I close my eyes and imagine it, I have a vision of the apocalypse, a perfect, pitch-black storm of utterly incomprhensible, pathologically difficult to debug code.
I was just looking at random PHP plugin code the other day, and it was, frankly, crap. But that's because most code is crap. Including my own. It is, sadly, the statistical norm. That's why sites like The Daily WTF are guaranteed to have more material than they can possibly ever publish for the next millennia. (Note to self: invest in this website). I can only imagine what that PHP plugin code would have looked like, had its developer been granted the ability to redefine fundamental PHP keywords and classes at will. These are the sort of thoughts that drive me to drink Bawls. And that stuff is disgusting.
You might say that PHP, sans the fundamental dynamic language ability to monkeypatch, is just another crappy Blub language. But there's also a ton of incredibly useful PHP code out there. So it seems to me that the ability to monkeypatch doesn't stop people from producing a huge volume of useful code, even in a kind of.. horrible language. Some of it is even good!
While I acknowledge the power and utility of dynamic language monkeypatching, I know enough about programmers -- myself absolutely included -- to know the vast majority of us have absolutely no business whatsoever re-designing a programming language. There's a reason some of the most deeply respected computer scientists in the world end up as language designers.
Perhaps then, given the risks, monkeypatching should mean reaching for the meta-hammer as infrequently as humanly possible. This is a position that Avdi himself espouses in a followup comment:
I'm afraid a lot of people have missed the actual meat of my argument -- that dynamic extension of classes is currently overused in Ruby, in ways that are:
- Needless - another technique (such as a mixin, or locally extending individual objects) would have worked as well or better.
- Overcomplicated - the use of a monkey patch actually created more work for the author.
- Fragile - the solution is tightly bound to third-party internals, reducing the usefulness of the plugin or gem because it is prone to breakage.
- Excessively wide in scope - by hardcoding extensions to core classes, the author takes the choice to scope the change out of the plugin/gem user's hands, further limiting utility.
My point is that there are alternatives - often alternatives which are actually easier to implement and will make your plugin or gem more useful to the user.
While I enjoy the additive nature of C# extensions, even those are enough to make me a little nervous, as mild as they are. Full-blown dynamic language monkeypatching goes even further; it might even be the ultimate expression of programming power. Is there anything more pure and godlike than programming your own programming language?
But if wielding that power doesn't scare and humble you a little, too, then maybe you should leave the monkeypatching to the really smart monkeys.
Yes monkeypatching is bad... but at least C# doesn't let you do it. :)
I think the point is always overlaboured about not reinventing the wheel. Yes its a waste of time, but those great programmers who wrote the first code were probably nothing special either... just as prone to making mistakes and doing things badly. Further they probably comprimised to find a best fit function to as many scenarios as possible.
Monkeypatching is bad, but reinventing the wheel is not necessarily that bad.
Yes.. I have made my own programming languages in my spare time. I do love reinventing the wheel... but on the other hand at least I used flex, bison and loads of the stdlib functions to do so... at least after the first time. :)
Still... why even bother with monkeypatching. Derive a new class or something... then at least if you reinvent a wheel or two, your buggy code is not likely to break other things left, right and centre. :)
Jheriko on July 14, 2008 6:11 AMAnders Heljberg is a respected language designer? WTF? He implemented Pascal, then Java, then Microsoft-Java (aka C#). When did he become a language designer in the same league as Wirth, Stroustrup, Codd, etc?
I wonder what's next? Duck typing probably - after all, no-one wants to write code that has to be designed in the new world of 'its so easy' .NET, not when they can just hack it about until it (fingers crossed) works like they want. I just pity the poor engineers who have to perform maintenance on all this code.
@AndyB:Anders Heljberg is a respected language designer? WTF? He implemented Pascal, then Java, then Microsoft-Java (aka C#). When did he become a language designer in the same league as Wirth, Stroustrup, Codd, etc?
Because God knows that AndyB is in a position to comment on the quality and the difficulty of writing 3 of the most well-known and used languages in the world.
And it's Heljsberg, not Heljberg.
I find it deliciously ironic that in the horrible language you refer to, you wouldn't experience this problem at all. It implements substr() just as syd suggests.
bobby on July 14, 2008 6:58 AMDon't be scared of a language's features. Learn how to use them.
A week ago a friend of mine related to me that a TCL primitive opened a program to a DOS attack. His answer was to rewrite the primitive in a manner immune to this. At the time he was in a reflective mood, and he did not call it monkey patching.
He also made a comment regarding teaching people, I could teach you TCL in five minutes. It would take you years to master it.
Ben T on July 14, 2008 7:22 AMOh it happened to me once in a huge JavaScript file, where I accidentally redefined window...it took me hours to track that!
Here's an advantage to not be a native english. You can use the variable class... window in your native language without problem :-)
Luc M on July 14, 2008 7:30 AMI've come to the conclusion that the best programmers are not the smartest ones. The 'mediocre' ones stick to regular practices and don't adopt new features all the time for the hell of it. Their code is more consistent, better written/commented. The smart-arse mavericks always seem to want to prove themselves in their code by unneccesarily using techniques that they don't fully understand themselves and don't appreciate the full ramifications. Then they think that documentation is not for them and two years down the line, it's their shite that someone else has to figure out.
Andy Turner on July 14, 2008 7:48 AMUrg, scary. The scope for those changes are way too wide. That would surely make debugging a nightmare. Never would I use those.
Kris on July 14, 2008 8:34 AMThe experience of maintaining C# code has just diminished to the equivalent of performing a colonoscopy. Way to go. VB is better.
Josh Stodola on July 14, 2008 8:41 AMThere's a reason some of the most deeply respected computer scientists in the world end up as language designers, and there is ALSO a reason why PHP is more popular, and has much more useful code, than Scheme.
The design goals of those scientists are strangely set apart from the design goals of programmers.
Anyway, those examples sound downright tame next to Forth, where you could change 0 to mean something else (for example). And, yet, Forth programmers learned to deal with it.
It's a matter, curiously enough, of tradition. Of learning what works and what doesn't, and passing that unconscious knowledge to your fellow programmers. We are seeing a spurt of innovation which will bring evolution to these languages, and like any evolution, some results are evolutionary dead ends. :-)
Hi Karl,
While your heart is in the right place, what you've done feels unnatural in C#. Keep going, but try to preserve and augment the C# coding culture rather than change the way C# reads.
Sub: Confusing. Perhaps you could rename Sub() to AsFormat() or FormatFor() because it then reads 'treat this string as a format string' (except only will format 1 value). C# dev's already grok string.Format(), so there's no point introducing new terminology, especially when it's overloading the same concept (conceptually). When you wrote Sub I thought SubString, not substitute (pesky Spartan programming techniques). Unless you've copied all of the overrides as well, you really need to have IFormatProvider overloads as well, and you've just made it a lot harder for code introspection tools like FXCop or even resharper.
DaysFromNow: There's already a really good string format for TimeSpan's, it's of the form [ws][-]{ d | [d.]hh:mm[:ss[.ff]] }[ws] and in a library you'd need to provide heaps of overloads.
Considering cacheing should ALWAYS be configurable, you could then load the value from the (dreaded) Xml config.
TimeSpan expirationWindow = TimeSpan.Parse(ConfigurationManager.AppSettings[UserCacheExpirationWindow]);
In practice I'd never write 3.DaysFromNow() or int counter; counter.DaysFromNow(); Use extensions as if you were adding a method to the class itself. Plug (perceived) holes in the shipped framework and make the syntax easier to read, not just different. DateTimeExtension.DaysFromNow(3) is a lot easier to understand in my opinion, but then is that easier than DateTime.Now.AddDays(3)? Not really.
another example of the kind of code I see all the time:
DateTime startOfMonth = new DateTime(DateTime.Now.Year, DateTime.Now.Month, 0, 0, 0);
versus
DateTime start = DateTime.Now.BeginningOfMonth(); // -- this is the extension method
An example that comes to mind is a JSON implementation I've seen in javascript that modifies the prototypes of all the built in types, breaking anything and everything and that uses a for(... in ...) loop and much more.
David on July 14, 2008 9:23 AMCould you use a color other than red to highlight the differences? It's very, very hard for us red-green colorblind folks to pick up red text mixed in with black text.
See point 3 here: http://jfly.iam.u-tokyo.ac.jp/color/#assign
Daniel Cormier on July 14, 2008 10:05 AMJeff I definetely agree with this one...monkeypatching is certainly more trouble than its worth.
o.s. on July 14, 2008 10:50 AMMulti-inheritance, function overloading, even lambdas used too much: all are Clever Code.
Clever Code is bad.
Richard Haven on July 14, 2008 10:59 AM@Daniel Cormier on July 14, 2008 09:05 AM
Could you use a color other than red to highlight the differences? It's very, very hard for us red-green colorblind folks to pick up red text mixed in with black text.
Unfortunately, to people who are not color-blind, highlighting in red comes naturally. Red is an extremely vibrant color and contrasts well with both black and white - more so than any other color.
Which color do you think he should have used? Or maybe instead of changing the color, he should have just made the highlighted text bold as well?
If you love strings (okay.. some strings are kind of sexy, but thats a completly other topic) why do you use them in a safe manner?
Don't do s.Length == 0 use String.IsNullOrEmpty(s) instead and put a little bit more safety in your code. And use curly brackets { } with if .. then .. else. And please. If functions return something follow the microsoft standard and only use one return per function. It's looking a bit longer but...
public static string Left(string s, int len)
{
string returnValue = ;
if (len != 0 !String.IsNullOrEmpty(s))
{
if (s.Length = len)
{
returnValue = s;
}
else
{
returnValue = s.Substring(0, len);
}
}
return returnValue;
}
@ James:
Prototype plays very nice with any other framework and has for some time. I've used it with YUI and jQuery. Several years ago (v. 1.4, I think) Prototype added some methods to Object.prototype at which point users rebelled and the author, Sam Stephenson, rightly removed it.
First off, the mere fact that the author of Prototype.js thought it would be a good idea to modify the base object prototype is a pretty clear indication of problems at the core.
The use (and overloading of) the $() function also indicates problems, given that the $() function has a specified use, according the the ECMA specification.
I have to say that Prototype.js is a perfect example of monkeypatching.
Tony on July 14, 2008 11:53 AMI have mixed feelings about monkeypatching.
Generally, it terrifies me. You can do that kind of thing in ActionScript/javascript, and I see it as potential security hole. If any random person can come by and redifine one of your private methods to do something different, then nothing is safe (though hopefully browsers will prevent things like that through XSS prevention).
However, it is also the only reason our product works. We depend on auto-generated SWF files built by a 3rd party tool, and the generated files are incredibly buggy. Monkeypatching allows me to fix the bugs at runtime by redefining the methods that don't work properly (and also allow me to fix a couple bugs in the built-in Flash UI components too).
Andy on July 14, 2008 12:28 PMI think stuff like this is really useful. It lets you fill in functions that should be there, and that cut down on the amount of code in many other places. Imagine not having a left function, and having to write those 6 lines of code 100 times througout your project in various places. The .Net API now contains the String.Contains function. Which is very useful to find out if a string is contained within another string. In .Net 1.1 Of course, there's always .indexof, but that doesn't really make it completely explicit what's going on. Using indexof is only really used because there wasn't something more correct to use. Allowing developers to extend the core functions provided by the API can be extremely useful. Just make sure you don't go over board, and start adding stuff like String.To1337Speak().
kibbee on July 14, 2008 12:58 PMThat code doesn't actually exist; it's only used as an example. I do agree with all the amendments / suggestions people have made, but critiquing dummy code is a little beside the point don't you agree? :)
Just write real abstract dummy code then instead of C#.
Jens on July 14, 2008 1:30 PMPatching built-in or widely used classes definitely seems evil but modifying specific instances of a class can be extremely useful. Which is a good time to remind ourselves that 99% of the time we're actually doing class-oriented and not object-oriented programming. What you really want to do is modify the behavior of objects and not the behavior of classes - otherwise they'd be a different class, wouldn't they?
Alright, in order to weigh up for all the {} flying around, I'll repost the final code from my post a few comments up (s/_/ /g):
(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, now we have a sane model where classes don't have special privileges, so methods can specialize on /any/ argument. Much better!
(Of course, the example code itself is rather silly, but it's just a direct translation of the original code.)
Mikael Jansson on July 15, 2008 2:41 AMAnd oh, you should define the interface for the methods -- generic function in Lisp -- as well, in order to be a good citizen.
(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.))
I prefer to call it duck punching, so that you get the appropriate violence invoked.
Note the problem with duck punching is that you immediately lose stability, unless you happen to know every other instance of duck punching being done anywhere else in your entire application.
Classic example:
http://weblog.raganwald.com/2008/07/ive-seen-things-you-people-wouldnt.html
And that's from a smart person who surrounds himself with smart people. Us lowly knuckle-draggers who don't regularly delve into the bowels of Rails are really in trouble.
There's also the problem of tracking changes, even if you manage to get a stable build. I discuss that scenario at length over at my blog.
http://enfranchisedmind.com/blog/2008/04/14/useful-things-about-static-typing/#comment-33124
Limiting duck punching to otherwise undefined functions is a *good idea*, because it gives you a limit on the ways you can shoot yourself in the foot, and a stp when you're about to.
Robert Fischer on July 15, 2008 3:02 AMI'd like some clarification about monkeypatching being a security hole ( @Andy ). Ok, while overriding a private method is defeating the whole purpose of code visibility and leads to some mess, it's an issue in code design, but as far as I know, private method were never meant as a security measure towards external exploits. Because anyway, if anyone gets as far as being able to override a private method through monkeypatching, you're still as screwed up as if monkeypatching wasn't available in the first place. It's as much a security hole as reflection is.
And as for the XSS attack, it's on browser side, and I don't see the point either. Security doesn't belong client-side, and as far as an XSS attack is possible it's already too late, the same nasty stuff could be done with or without monkeypatching.
Vincent on July 15, 2008 3:33 AM@Bobby:
I agree with Sub, it's a shame you can't re-use a static name on an extension method - the extension actually compiles, but in some situations you can't use it (it's really odd, almost seems broken).
Aside from that point, I agree there's value in keeping things the C# way, but I also think there's much to be had by expanding that as well. If we only followed the C# culture, we wouldn't have unit tests until 2008, would be using datasets instead of NHibernate, and would use XML for everything. Some of the extensions that I added were heavily influenced by Ruby, which has a far more open concept and far more elegant syntax. Writing a view in rails is easily an order of magnitude cleaner than in ASP.NET MVC w/C# or VB.NET. For this reason, I think it's good to challenge that status quo, so to speak.
To keep the C# culture the way it is for the sake of making it easy is highly questionable. The alpha dogs are pushing hard towards two extremes - specification-based languages (Spec#), and dynamic languages (Boo, IronPython and IronRuby). I'm not saying C# will die out, but I do think you'll see more and more people take up specialized tools for the job (O/R mapping for your DAL, Spec# for your domain layer, and IronRuby for your presentation - heck most of us are already doing this in some form or another).
C# is by no means a holy grail, and the C# culture - by it's very strong ties to the .NET culture - is far from perfect. To quote a former Microsoft developer, there are too many people these days calling themselves programmers who have no inclination to test the limits of their languages.
Karl on July 15, 2008 6:28 AM@Andy and Vincent,
In actionscript 3 the compiler won't allow to override a private function, and the core features that are considered essential are defined as internal or as final which sets a common ground for future versions of the api.
@Kibee, well it's actually not as bad to have a toL33tSp3aK as you can be sure that the language will, most likely, never have a native method with that name.
I honestly think that all of this has been lived through by the older generation of programer with Forth and Lisp, but somehow there's a lot of new programmers that are pushed by the corporate world to just code as fast as possible and disregard whatever consequences the future might bring to their code.
pedro on July 15, 2008 6:51 AM@Karl I appreciate your response and surely didn't mean to imply that you shouldn't push the boundaries of the language.
It has nothing to do with the technology you use, and everything to do with how you read code (code clarity).
_I_ don't want C# code that reads like ruby. I want to borrow the concepts, but not necessarily names, especially redefining existing names and concepts. That just makes code harder to read and maintain by someone else.
To me, the point of coding using the accepted idioms is because you accept that language, and you want to read the code, and not read a java person writing ruby, or a ruby person writing java, etc. I write c# to look like c# and javascript like other javascript, and python to look like python. If you want to write python, why are you writing lisp?
There is truly a staggering amount of thought that goes into language design, despite what most of the punters on this blog go on about.
By all means make it better, just be careful you don't invent an unneeded wheel. :)
bobby on July 15, 2008 7:10 AM@pedro
What are you on about regarding Lisp and Forth? Yeah, those old people don't know any better
Mind clarify yourself?
Mikael Jansson on July 15, 2008 9:26 AMOld versions of Fortran let you redefine numeric constants in certain cases. The ultimate in Monkeypatching.
http://coding.derkeiler.com/Archive/Fortran/comp.lang.fortran/2005-01/0487.html
Oliver Jones on July 16, 2008 6:47 AM@Mikael
I meant that the older generation of programmers (I'm 27 and coding since 11) have been through this before and they managed to handle it (and that's a good thing :) ) and provide us with an insight on how to work with overloading, overriding and so on... Recent languages are usually stricter because _I_ believe the people that designed them have had these problems in the past.
In a perfect world we'd all be coding away in Lisp (a language that allows to redefine it's own syntax) and be awesome programmers, but we're not.
So to cut to the point, I feel that the biggest problem is that the new generation, and even mine, have somehow forgotten to look to the past for answers and somehow keep forgeting that 'monkeypatching' isn't a new problem and has indeed been tackled by plenty of programmers before us. Throw that together with the ever growing demand for shorter development cycles and programmers that don't work hard at understanding and you have the mess you have now.
@queisser
What you really want to do is modify the behavior of objects and not the behavior of classes - otherwise they'd be a different class, wouldn't they?
Well, sometimes you really want to modify the behaviour for a whole class - like, when the objects of this class are instanciated by a library or framework.
@all_the_bondage__discipline_programmers:
As usual, the problem is not with the feature, but with how it's effectively used.
Bruno on July 17, 2008 7:49 AM@KG on July 14, 2008 10:08 AM
Pretty much anything. Even a lighter shade of red would be better.
Daniel Cormier on July 18, 2008 2:58 AMThe problem isn't monkey punching. The problem is programmers doing stuff that doesn't work.
Several people have mentioned Forth allowing this. Doesn't C allow similar things with macros and the preproccessor?
Forth lets you redefine absolutely anything you want to. For example:
5 constant 4
After this is compiled, every place your source code has the number 4 except inside strings, it will compile a 5 . I can't think of any situation where this is useful, but it's available. Every word can be redefined.
: DUP DROP ;
: : BYE ;
The only place this gets used is for compiling incompatible code together. Code written for different Forth versions can be run together, you set up different meanings for the same words in different scopes.
Forth79 definitions
: NOT 0= ;
Forth83 definitions
: NOT invert ;
The same word got defined to do two different things, one was a logical not that returns a true flag if the flag it receives is zero.
The other is a bitwise invert that flips every bit. If you have ancient Forth code from two different sources and you want to use them both withour rewriting, you probably can.
All this power that hardly ever gets used. Why not? Because you usually don't have a real use for it. But there's no need or reason to add code to the compiler to prevent it, either.
If you make a new function that does something new, usually you do better to give it a new name and not the same name as something else. Less confusing that way. You say what you want and the compiler does it. Simple and easy. You have to know what you want, but when you do know what you want you can get it a lot easier.
J Thomas on July 23, 2008 12:49 PMYeah, the scope thing scares me too. I think the fundamental problem is that without monkeypatching, I can always tell where the code I'm calling is defined -- find the definition of Class X, find the function, bam. But there's no obvious browser to show me where the cobbled-on function is defined, and if I don't extensively segregate my namespaces (I routinely have entire 20-class projects where all files are in the same namespace), I could really shoot myself in the foot. It's an interesting idea, but I think we need more experience with the idiom.
James on July 31, 2008 9:15 AMYou're evil. Seriously.
Stefan on August 2, 2008 4:55 AMMonkeypatching can be incredibly dangerous in the wrong hands
So can a sharp knife, yet even amateur chefs need one.
Can you imagine trying to cook a meal, or change your oil, or tune a violin, or climb a rock wall, using only Fisher-Price tools which are so blunt that they cannot possibly be dangerous in the wrong hands?
And all of those things are done every day, by *amateurs*. We're supposed to be professionals. Whatever happened to learning how to use your tools properly?
Monkeypatching in the wrong hands sounds much less scary than having to use a language explicitly designed to prevent me from doing anything which other people wouldn't be able to do without hurting themselves. There's a reason nobody uses Pascal and Ada today, not even the big risk-averse companies.
Ken on April 8, 2009 11:50 AMEven if C# allowed Monkey Patching, unless you were defining your Monkey Patching in a system namespace somehow, it would be easy to find by checking out what using directives you had to find the offending overwriter.
Extension methods in C# roll like that. You don't get them at all unless you opt in for them. You can't universally add .Left to string that works in code you don't control unless said code subscribes to it.
Aaron Erickson on February 6, 2010 10:37 PMNow imagine monkey patching in a programming language with true multiple class implementation inheritance _and_ dynamic multiple argument method dispatch... Common Lisp was fun!
Cool - I decided to write a String.Right() extension method just last week: http://harriyott.com/2008/07/extension-methods-in-c-again.aspx
Simon Harriyott on February 6, 2010 10:37 PMSeveral years ago (v. 1.4, I think) Prototype added some methods to Object.prototype at which point users rebelled and the author, Sam Stephenson, rightly removed it.
Um if you are talking about the extensions to Array, then yes there was a (mini)rebellion by some. But they were not taken out as they only broke badly written code which did not follow standards. See Associative Arrays considered harmful by Andrew Dupont for more info.
The comments to this entry are closed.
|
|
Traffic Stats |