Can't say I agree with this. A decent IDE has no problem tracing usage of dynamically generated methods etc. I know the JetBrains stuff can for Python and Ruby.
Why is this hard to believe? There are relatively well-established patterns for big libraries like rails, and big libraries like rails are also part of the reason people turn to such IDEs. Such cases could be pretty easily handed with some custom code. It doesn't have to be 100% generic and 100% perfect to be useful.
I don't want to be dependent on a single IDE to write or maintain code. Besides the annoyance of not getting to use what I want, what happens when the IDE stops being supported, but you still have to maintain the code?
thats a common argument against IDEs but i think it doesnt reflect reality.
The most powerful IDEs like Visual Studio, Eclipse, XCode, IntelliJ IDEA have all been around for more than a decade and wont be going anywhere. Of course if you use some less popular product it might get abandoned, but the same can happen to less popular open source projects.
All the Textmate users now seem to be burned and always fear the software they use will die, so they stick to open source tools only. Those are okay for dynamic languages and smaller scope projects but i still find IDEs indispensable for large codebases and languages like C# or Java.
While RubyMine does an almost unbelievably good job of finding the declarations for dynamically generated methods, it is _far_ from perfect. I might even say it doesn't do it very well, except that what it does do is way more than I would expect it to be able to.
"When I first came onboard with Loki, the Alpha Centauri Plantary Pack was my first porting project. I didn't know what to expect from commercial game code, but I sure wasn't expecting what I found in the SMAC codebase. Tens of thousands of lines of assembly code was in SMAC, some of which was self-modifying. I spent most of my time looking at memory in the debugger and flipping bits."
This has nothing to do with static vs dynamic typing. In Java, for example, you can easily invoke a method named by a string that's not defined at compile time (using the reflection api).
But since you can't define methods using reflection, I'd say this isn't used much and isn't really a problem in Java. Are you really having trouble decyphering a Java function relying on home-made dynamic dispatch?
But yeah, I'd say it's more about http://en.wikipedia.org/wiki/Dynamic_dispatch than static/dynamic typing. You can have dynamic dispatch in a statically typed language. I don't know how OP would feel about virtual methods in C++ since he resists using an IDE.
Counterpoint: Macros. Though you could preprocess the code and grep that, it wouldn't work too well. Eclipse-et-al can give you a sort-of-works-most-of-the-time navigation features that work with the preprocessed code, which can overcome the macro problem, mostly.
Functions passed as arguments can be traced the same way you trace values. You find all invocations of the surrounding function and see what function arguments are being passed in.
It's more involved than just grepping for the name, but the same is true of tracing where any other argument value came from.
functions passed as arguments are fine, because something needs to be passing them. While the function invocation site itself is dynamic, the source of the call isn't.
Basically the important aspect is that even if you have higher order functions, the things passing the functions to these higher order functions will still be greppable.
I have unfortunately created an annoying case of this, in which a bunch of thunks are put in a list and invoked en masse. If any one of them has a problem, it's a bit of a problem to sort out where it was created. This is exasperated by the fact that functions do not know their own names in Lua.
Lua 5.1.4 Copyright (C) 1994-2008 Lua.org, PUC-Rio
> function f(g) g[1]() end
> function thisFunctionCrashes() undefinedVariable() end
> f({thisFunctionCrashes})
stdin:1: attempt to call global 'undefinedVariable' (a nil value)
stack traceback:
stdin:1: in function '?'
stdin:1: in function 'f'
stdin:1: in main chunk
[C]: ?
In the example, the source line is available, but in the genuine article the thunks are for native functions.
It has nothing to do with being dynamic, it is about names. The article insists that you need to be able to grep for the identifier, but you don't have the identifier. So you have to grep for the function you are in, find all the places it is called from, and then sort through all the functions passed into it as arguments. The function passed in as an argument is often an anonymous function, so it still won't have an identifier anyways. The entire concept is just plain nonsense.
While I agree with this idealistically and wish that all the code I've come across adhered to published interfaces, unfortunately that hasn't been the case.
Published interfaces may also not be possible in certain code bases or frameworks -- think of a Chef recipe for instance where most (ruby) code is being written to directly interact with the system rather than provide a service to other parts of the system. Dynamically generating variable names and key/value pairs in Chef recipes is a common pattern I've seen which makes grepping and isolating issues difficult if not impossible.
That's sufficient when the code is flawless and you never have to look at it. I.e. pretty much never.
The rest of the time it is still a huge benefit to be able to easily navigate the source. And frankly, a lot of the time the source is a more concise source of truth than decently written documentation.
Yes, grepping for a function and not finding it anywhere in the codebase is very annoying. I've had to maintain this sort of thing before. It's like in bash saying `${FOO_$BAR}` (if such a thing is possible). It gives me shudders.
I also agree that Rails gets a pass. It's a little different when you have a stable (well, sort of :-) API with dozens of books and thousands of blog posts. It was annoying to learn what was going on at first, but that knowledge is more long-lasting so worth a bit more pain.
Agreed, though I would say that if you take away the well known Rails idioms that fail the test, there are still some pretty egregious violations.
For example, try to step through the code that figures out which validation callbacks to trigger for an ActiveRecord model. You'll be led through at least 3 dynamically eval'd methods.
I had to do this recently and probably would have given up had there not been Foo.method(:bar).source_location to point me in the right direction.
Agreed; although the only thing worse than not finding it is finding it all over the place. I've worked with Python code where the developer liked to use member names like "type" and "id" -- my 'ag' output is useless in that case.
I'm still trying to figure out how Python projects scale to any appreciable size; I suspect maybe they don't. I've worked on several million-line codebases in statically-typed languages, are there any truly large Python projects?
choosing module and function names is an acquired art in python. I usually grep my entire code base (and ensure nothing matches) before I add a new public name.
> I’m sure by this point, a few of you have thought “Hey, but Rails fails the Grep Test!”. This is absolutely true, most notable due to the dynamic find_by_ finders, and the dynamic _path URL path generators.
OK, sure, but after you've grokked the first "find_by" usecase...do you really need documentation for all the other kinds of "find_by"'s that you'll use?
In any case, I'd agree that meta-programming is too often abused, but the proposed grep test is far too strict. And, inability to create complete documentation for every single kind of method token is not really the main reason to avoid meta-programming...I'd say performance and the propensity for abuse are better motives.
This pull request re: removing most of Rails' dynamic finders in 4.x covers the topic nicely:
> In any case, I'd agree that meta-programming is too often abused, but the proposed grep test is far too strict.
I agree. In a CMS I'm coding, the core MVC-like mark up tags work by using function tables in Go. Such code would fail the grep test - but it makes a lot of sense in the project and it actually very readable as well. In fact in that particularly use case, not only is the code more readable, but it's also more efficient.
But this is the age old problem with having rules in languages (both human and programming) - more often than not, there are exceptions that are perfectly legitimate use cases.
An important distinction is between mature (or matur-ish) library code and your consuming code base:
- library code publishes an interface and can do what it wants
- your client/application code should be greppable
There isn't a sharp distinction between the two, but it's a pretty big headache if developers are using heavy metaprogramming everywhere in your project.
This is dumb. Most languages support some way of autoloading classes. If you don't understand that then you won't be grepping the right file. As such this is not a valid test for readability because understanding how classes is loaded is assumed.
I have no problem with autoloading classes (though I suppose me including "modules" in the list might've implied that). If a class is autoloaded, I can still find its declaration and implementation using grep.
As for "Not grepping the right file", I meant it should be findable in a project-wide grep, not in the file I assume it to be in.
One thing that can often help with this is to have a good REPL. With it you can dive into and explore the code as it is at runtime. For example, a common way that I figured out Rails was to open the console and use obj.methods to see what methods are available.
Other languages in which I've enjoyed REPLs include Python, JavaScript and Haskell. Two languages I have not found a good REPL for are Java and C++. Is anything of this sort available there?
I think this is a legitimate issue, though I'm not sure I really agree...there just seem to be too many places where it provides too much utility. That said, I tend to change my opinion on the topic fairly frequently, based upon whether I was last writing something where dynamic function declaration was convenient or last looking for where a function was declared.
One thing I think the post might emphasize more thoroughly is that dynamic function invocation (at least when the function is defined in the project scope), is probably far more problematic than dynamic function declaration, particularly if sometimes the function is explicitly invoked, and sometimes dynamically. In this situation, I'll generally try to document that the function is dynamically invoked with the function declaration, but I'm always unsure what exact information I should put there. Simply saying something like "Dynamically invoked -- grep will not find all usages" is a minimum, but I often want to add more than just that.
Agreed. I do comment on dynamic invocation being more problematic when I say "As a broad generalization, I would say dynamic declaration is occasionally worth the tradeoff, but dynamic invocation is almost never worthwhile.", but it perhaps should've been a larger part of the thesis.
So basically I can't commit anything containing the slightest form of metaprogramming? Seems a bit extreme to me. A solution is to go for the more concise and robust metaprogrammed piece of code (the example of the form with the User object is a good one) and add a comment mentioning the methods called in there.
The "smartass" way of programming is sometimes overused but it does have its benefits. When you're metaprogramatically setting the attributes on the User, you're also avoiding needless and error-prone repetition and making sure that this central piece of code will either crash all the time or work all the time for all attributes. This has tremendous value.
So while I understand the point about this article, I might want to add a pinch of salt to the dogma underlying it.
Your example of adding a comment mentioning the methods called in there would indeed pass the Grep Test, and is a reasonable compromise when there is a real call for dynamic declaration.
I think there are definite ways of adding metaprogramming functionality without breaking this test. For instance, in the first JavaScript counterexample, if the iteration was over [{attr: "position", fn: "getPosition"}, {attr: "direction", fn: "getDirection"}] instead, the Grep Test passes, and you get much of the same benefits, with a very minor duplication that I'd argue is worth the cost.
I work in Perl for the most part, and the alternative I've been using lately for functions is to assert that for a given generated sub, the text 'sub routine_name' exists literally in the code somewhere. This takes two forms in my case:
generate_from_spec then strips off the 'sub ', and errors out if it is not found.
The second case I have is a function that itself then generates a function, but is trying to look like a sort of normal function itself, in which case I end up with:
"abusing" Perl's => operator, which functions like a comma except that it forces stringification of the left argument, to once again make the literal, greppable "sub routine_name" appear in the codebase. Here "routine_name" is then just a standard string argument, the alterative being
generate_sub "routine_name",
which is then harder to grep for. (Still possible, obviously, with a different grep query, but only if you already know up front you need to add the other possibilities.)
Note this actually goes a step beyond what you are proposing in that it makes the declaration site clear; my counterproposal for your JS case would be
I'm not sure I totally disagree with you, but I have to at least disagree with part of your argument.
> Seems a bit extreme to me.
That reasoning has absolutely zero argumentative value behind it, in any context that it's used. It ought to be treated like a logical fallacy.
> add a comment mentioning the methods called in there.
The problem of comments getting out of sync with code is omnipresent. "Never fail the grep test" seems like a much more easily-enforced and -maintained practice (both for yourself, and for teams) than what you're suggesting.
Yes yes yes. It's important to have code standards that enable code to be easy to understand easy to navigate.
I work in C++ all day and loathe when functions are fully implemented in the class declaration. Keeping them seperate with full ClassName::FunctionName(...) scoping makes finding functions exceedingly simple and friendly. Keep implementations separate means the full class declaration is easy to read, parse, and understand. I don't want to scroll through hundreds of lines of code just to see what functions are available.
I normally do C++, but started doing some Typescript (Javascript) the other day. Like Java, this doesn't make external implementation easy.
I found myself using a pattern of declaring a one-off interface for complex classes and then implementing it in the real class. I'm still undecided when/if this is a useful thing to do.
This seems like a great case for solid comments in the code, as long as they're up-to-date and you don't have a huge number of permutations. I know we all like to crap on code comments here, but if you had say 5 possible permutations that this code supported, a programmer could easily throw a comment above it with each of the supported functions, comma-separated. Then grepping would indeed find them.
Ehhhhhhhh not really. If you're using metaprogramming or abstract programming of any kind, it's usually for a good reason, and DRY in itself is worth simply not being able to find every single reference to your metaprogrammed item in your codebase.
It's an artefact of the design. You simply need to know how the abstraction is done, and then you look for invocations of that abstract more general form. It's not that difficult, and if it's being done, it is almost without exception a better way to do the thing.
If metaprogramming is not a better way to do the thing being done, and adds confusion and decreases reusability, then you shouldn't do it, but that's a tautology and doesn't mean we have to be able to grep for everything we ever write.
Honestly, in my professional opinion probably 60% of the usages of metaprogramming seen in the wild fail the "for a good reason" test pretty badly. Most of the time it's just the author being clever to save a few lines.
But I don't see why metaprogramming should inherently fail the grep test anyway. Sure, your property/thingy/whatever (an access method for a field automatically generated from a database, say) might be generic and generated at runtime, but its name should still be written down somewhere authoritative, even if it's in a schema or (if nothing else) a documentation file. That stuff should still be in your tree and greppable, and if it's not your code has discoverability problems.
The "grep test" doesn't just help humans and IDE's. Closure Compiler's ADVANCED_OPTIMIZATIONS mode (https://developers.google.com/closure/compiler/docs/api-tuto... ) only works with JS code that can pass the "grep test". Setting or getting properties using dynamically generated property names won't work (even if Closure Compiler could understand your intent, it would have to know at runtime which minified name the concatenated parts should result in).
So in this case, writing longer, "grep-friendly" JS code can actually reduce the size of the JS payload you serve to your users.
No, you can do this sort of madness in C too via function pointer tables. Give the function pointer field in your struct a commonly-grep-spammed name like "close" and watch your readers go mad trying to figure out what xxx->close() does.
I've done a bunch of work with statically typed languages and am not about to argue that dynamic languages can be as easily navigated or statically analyzed by IDEs as statically typed ones can.
That said, dynamic languages still have a great deal of value, and a significant portion of the programming population uses them, so I still think it's worthwhile to set down some useful rules of thumb.
So, basically, everyone who is capable of producing excellent code is bared from using a powerful technique, largely because some people can't understand the code they produce.
It's a difficult trade-off, one one I've had to make calls on several times. Do you use the full capabilities of incredibly gifted and talented programmers, and then allow code into your codebase that maintenance programmers can't understand?
I think the best solution is to have the better ones mentor the lesser ones. It takes time away from coding in the short-term, but it is a good investment in your people in the long-term. Metaprogramming is something that any good programmer can understand if they are just taught well, just like all the other difficult parts of programming that people routinely like to ban: pointers, goto, lambdas and functional programming, C macros, Lisp macros, etc. Using them correctly really helps a lot, using them incorrectly can hurt a lot.
The solution is to learn how to hit the nail with the hammer, not your thumb.
I think "do the work of hundreds" is a little bit generous. It does save a little time, but does it really save so much time as to boost productivity 100x?
Most teams eventually need to on board new people, though.
Besides that, building teams of generalists is far easier if it's easy to dive into new code bases. Even with small teams highly specialized on individual projects, there are lots of advantages to having people be comfortable digging into other teams' code, which is much easier if it's more discoverable.
I must be pedantic and separate the definition of "metaprogramming," and "dynamic programming;" of which this is the latter.
The reason this difference is important is that metaprogramming is structured and explicitly defines the change in the program's semantics.
Thankfully I have never seen code like what was posted in my experience with Python so far. Explicit is better than implicit which the code example in this post would fail to pass IMO. Dynamically dispatching to names that don't exist in a class' published interface at call-time is a big no-no in my book.
Although practicality beats purity.
I haven't seen it yet but that doesn't mean there is a practical reason to use this method of dynamic dispatch. If there were one and it gets a problem solved NOW rather than waiting to find a better solution -- it might be worthwhile.
However it's a price you have to pay.
I think the grep-test is at least a good way to test the waters with a bit of code. I don't think it's a universal end-all-discussions rule.
One should not get too dependent on just one tool as Grep, sacrificing some useful features of dynamic languages... In the Javascript example, (and possibly others), it is still possible to easily find where the method was defined - open a debugger, add a break point on console.log(ray.getPosition()); and step in to it.
there are many other tools to help you, for example, many modern IDEs have powerful inspection features these days.
There is always going to be code that takes more than grep and a glance to understand. Sometimes you won't fully grok it until you've stepped through it line by line, even without "tricks" like metaprogramming. And more often than not the "tricks" make it more concise, less prone to bugs, and easier to understand.
It's madness to classify all use of metaprogramming as abuse. Perhaps you need to pour a glass of wine and learn to savor the source code, and to appreciate the power of modern programming languages.
172 comments
[ 2.6 ms ] story [ 228 ms ] threadAll the Textmate users now seem to be burned and always fear the software they use will die, so they stick to open source tools only. Those are okay for dynamic languages and smaller scope projects but i still find IDEs indispensable for large codebases and languages like C# or Java.
But yeah, I'd say it's more about http://en.wikipedia.org/wiki/Dynamic_dispatch than static/dynamic typing. You can have dynamic dispatch in a statically typed language. I don't know how OP would feel about virtual methods in C++ since he resists using an IDE.
You can do about anything you want with cglib / Javassist. Dynamically subtyping or byte code re-writing a class is how many Java ORMs work.
It's more involved than just grepping for the name, but the same is true of tracing where any other argument value came from.
Basically the important aspect is that even if you have higher order functions, the things passing the functions to these higher order functions will still be greppable.
A published & concise interface wins over an undocumented redundant interface. Explicit-only just takes you down the path towards Java and COBOL.
Published interfaces may also not be possible in certain code bases or frameworks -- think of a Chef recipe for instance where most (ruby) code is being written to directly interact with the system rather than provide a service to other parts of the system. Dynamically generating variable names and key/value pairs in Chef recipes is a common pattern I've seen which makes grepping and isolating issues difficult if not impossible.
But I don't think the OP argued against either documentation or fixing bugs, so I fear that both of our comments are irrelevant.
The rest of the time it is still a huge benefit to be able to easily navigate the source. And frankly, a lot of the time the source is a more concise source of truth than decently written documentation.
I also agree that Rails gets a pass. It's a little different when you have a stable (well, sort of :-) API with dozens of books and thousands of blog posts. It was annoying to learn what was going on at first, but that knowledge is more long-lasting so worth a bit more pain.
For example, try to step through the code that figures out which validation callbacks to trigger for an ActiveRecord model. You'll be led through at least 3 dynamically eval'd methods.
I had to do this recently and probably would have given up had there not been Foo.method(:bar).source_location to point me in the right direction.
I'm still trying to figure out how Python projects scale to any appreciable size; I suspect maybe they don't. I've worked on several million-line codebases in statically-typed languages, are there any truly large Python projects?
OK, sure, but after you've grokked the first "find_by" usecase...do you really need documentation for all the other kinds of "find_by"'s that you'll use?
In any case, I'd agree that meta-programming is too often abused, but the proposed grep test is far too strict. And, inability to create complete documentation for every single kind of method token is not really the main reason to avoid meta-programming...I'd say performance and the propensity for abuse are better motives.
This pull request re: removing most of Rails' dynamic finders in 4.x covers the topic nicely:
https://github.com/rails/rails/pull/5639
I agree. In a CMS I'm coding, the core MVC-like mark up tags work by using function tables in Go. Such code would fail the grep test - but it makes a lot of sense in the project and it actually very readable as well. In fact in that particularly use case, not only is the code more readable, but it's also more efficient.
But this is the age old problem with having rules in languages (both human and programming) - more often than not, there are exceptions that are perfectly legitimate use cases.
As for "Not grepping the right file", I meant it should be findable in a project-wide grep, not in the file I assume it to be in.
Other languages in which I've enjoyed REPLs include Python, JavaScript and Haskell. Two languages I have not found a good REPL for are Java and C++. Is anything of this sort available there?
One thing I think the post might emphasize more thoroughly is that dynamic function invocation (at least when the function is defined in the project scope), is probably far more problematic than dynamic function declaration, particularly if sometimes the function is explicitly invoked, and sometimes dynamically. In this situation, I'll generally try to document that the function is dynamically invoked with the function declaration, but I'm always unsure what exact information I should put there. Simply saying something like "Dynamically invoked -- grep will not find all usages" is a minimum, but I often want to add more than just that.
The "smartass" way of programming is sometimes overused but it does have its benefits. When you're metaprogramatically setting the attributes on the User, you're also avoiding needless and error-prone repetition and making sure that this central piece of code will either crash all the time or work all the time for all attributes. This has tremendous value.
So while I understand the point about this article, I might want to add a pinch of salt to the dogma underlying it.
I think there are definite ways of adding metaprogramming functionality without breaking this test. For instance, in the first JavaScript counterexample, if the iteration was over [{attr: "position", fn: "getPosition"}, {attr: "direction", fn: "getDirection"}] instead, the Grep Test passes, and you get much of the same benefits, with a very minor duplication that I'd argue is worth the cost.
The second case I have is a function that itself then generates a function, but is trying to look like a sort of normal function itself, in which case I end up with:
"abusing" Perl's => operator, which functions like a comma except that it forces stringification of the left argument, to once again make the literal, greppable "sub routine_name" appear in the codebase. Here "routine_name" is then just a standard string argument, the alterative being which is then harder to grep for. (Still possible, obviously, with a different grep query, but only if you already know up front you need to add the other possibilities.)Note this actually goes a step beyond what you are proposing in that it makes the declaration site clear; my counterproposal for your JS case would be
and using string manipulation to do whatever you need to do to get the right info out of the function name.> Seems a bit extreme to me.
That reasoning has absolutely zero argumentative value behind it, in any context that it's used. It ought to be treated like a logical fallacy.
> add a comment mentioning the methods called in there.
The problem of comments getting out of sync with code is omnipresent. "Never fail the grep test" seems like a much more easily-enforced and -maintained practice (both for yourself, and for teams) than what you're suggesting.
I work in C++ all day and loathe when functions are fully implemented in the class declaration. Keeping them seperate with full ClassName::FunctionName(...) scoping makes finding functions exceedingly simple and friendly. Keep implementations separate means the full class declaration is easy to read, parse, and understand. I don't want to scroll through hundreds of lines of code just to see what functions are available.
I found myself using a pattern of declaring a one-off interface for complex classes and then implementing it in the real class. I'm still undecided when/if this is a useful thing to do.
It's an artefact of the design. You simply need to know how the abstraction is done, and then you look for invocations of that abstract more general form. It's not that difficult, and if it's being done, it is almost without exception a better way to do the thing.
If metaprogramming is not a better way to do the thing being done, and adds confusion and decreases reusability, then you shouldn't do it, but that's a tautology and doesn't mean we have to be able to grep for everything we ever write.
But I don't see why metaprogramming should inherently fail the grep test anyway. Sure, your property/thingy/whatever (an access method for a field automatically generated from a database, say) might be generic and generated at runtime, but its name should still be written down somewhere authoritative, even if it's in a schema or (if nothing else) a documentation file. That stuff should still be in your tree and greppable, and if it's not your code has discoverability problems.
So in this case, writing longer, "grep-friendly" JS code can actually reduce the size of the JS payload you serve to your users.
You can grep for every instantiation of the struct in the code-base.
If anything, macros are more problematic:
That said, dynamic languages still have a great deal of value, and a significant portion of the programming population uses them, so I still think it's worthwhile to set down some useful rules of thumb.
I used to think this too.
It's a difficult trade-off, one one I've had to make calls on several times. Do you use the full capabilities of incredibly gifted and talented programmers, and then allow code into your codebase that maintenance programmers can't understand?
Tricky.
The solution is to learn how to hit the nail with the hammer, not your thumb.
(See Yegge's grok project, unfortunately he doesn't blog anymore)
Making code that passes the grep test allows many more programmers who are vaguely familiar with the codebase to make changes.
Making code that fails the grep test allows a team of a few highly skilled developers who know the codebase inside and out to do the work of hundreds.
It's like mathematical notation, you generally need experience in that sub-branch of mathematics to understand the notation.
Besides that, building teams of generalists is far easier if it's easy to dive into new code bases. Even with small teams highly specialized on individual projects, there are lots of advantages to having people be comfortable digging into other teams' code, which is much easier if it's more discoverable.
The reason this difference is important is that metaprogramming is structured and explicitly defines the change in the program's semantics.
Thankfully I have never seen code like what was posted in my experience with Python so far. Explicit is better than implicit which the code example in this post would fail to pass IMO. Dynamically dispatching to names that don't exist in a class' published interface at call-time is a big no-no in my book.
Although practicality beats purity.
I haven't seen it yet but that doesn't mean there is a practical reason to use this method of dynamic dispatch. If there were one and it gets a problem solved NOW rather than waiting to find a better solution -- it might be worthwhile.
However it's a price you have to pay.
I think the grep-test is at least a good way to test the waters with a bit of code. I don't think it's a universal end-all-discussions rule.
It's madness to classify all use of metaprogramming as abuse. Perhaps you need to pour a glass of wine and learn to savor the source code, and to appreciate the power of modern programming languages.