Magic is often used to describe confusing code
quii.co.uk
quii.co.uk
Languages permit varying levels of this sort of magic, ranging from the Ruby extreme of dynamically rewriting classes and inheritance hierarchies on the fly, to the pretty-much-no-magic of C. (C qua C anyhow. Preprocessor can get fun.)
I think this is one of the worst sorts of magic, and I've generally tried to arrange my code so that even when I'm using a dash of magic, the pathway back to where everything came from is still fairly clear. (Usually this can be done just by dealing with the fact you may need to use a few more keystrokes to leave the breadcrumb trail back to the implementation. Sometimes simplifying a bit of functionality down to zero bytes in the source code is going a step too far.)
I'll also observe that having used a number of both high-magic and low-magic languages, in terms of costs/benefits, I am generally underwhelmed by the benefits of the magic, and find the cost generally go up more quickly than most people realize as source code size goes up. I generally prefer low-magic languages. With just a bit of thought, you can still get a huge amount of power without the costs. (For instance, believe it or not, Haskell is a nearly-zero-magic language by this definition. For all the crazy stuff it seems to be able to do, you can always see what's going on just by following back the symbol definitions, with the exception of just a finite bit of syntax sugar implemented by the compiler, the most important of which is generally tutorial-level.)
[1] https://en.wikipedia.org/wiki/Lions%27_Commentary_on_UNIX_6t...
/*
* If the new process paused because it was
* swapped out, set the stack level to the last call
* to savu(u_ssav). This means that the return
* which is executed immediately after the call to aretu
* actually returns from the last routine which did
* the savu.
*
* You are not expected to understand this.
*/
if(rp->p_flag&SSWAP) {
rp->p_flag =& ~SSWAP;
aretu(u.u_ssav);
}I call it "grep-resistant programming"
Reminds me of the quote “In Smalltalk, everything happens somewhere else “—Adele Goldberg.
I call these kinds of things, “action-at-a-distance,” or if I’m feeling snide, “COMEFROM code.” The thing is, action-at-a-distance is exactly what I want when I’m trying to separate concerns.
In Rails, for example, you can decorate a controller method using `before` at the top of a controller. To the person unfamiliar with the spell, this makes perfect sense:
def index
authenticate_current_user
render something_or_other
end
But this is magic: before authenticate_current_user, only: [:index]
# ...lots of code...
def index
render something_or_other
end
That is perfectly grep-able if you know to grep a method name, and if you have a small controller class it ought to stand out, &c. &c., so it rarely offends. But it’s still magic.It’s a win if we want to make our index method as simple as possible.
And that goes for all of Rails, really. The very idea of “convention over configuration” is a wholesale embrace of magic. In Ember:
userService: service(),
How is it that if you write `service()` without naming a service, the function defaults to the name of the property it is bound to? That makes absolutely zero sense in JavaScript, and the more you know about JavaScript, the more magical this becomes.There is, in fact, no way to find the code that does this unless somebody whispers in your ear that Ember does compile-time chicanery, and you aren’t really programming in JavaScript, you’re programming in Ember, a language that has a lot in common with JavaScript, and that compiles to JavaScript.
You can decide for yourself if this is a win. Lots of people like it.
For instance if a method relied on it being modified at run time by it's caller to ensure sanity of it's input I would call it magical.
That said, there is such thing as not using language features that your team isn't familiar with: There is cognitive cost in, say, understanding higher kinded types, adding cats as a project dependency, or, god forbids, shapeless. There is such thing as excessive complexity, or using features that make anyone that didn't write the code suffer to understand it.
So yes, there are definitely good reasons to say that something is magic, and wishing for a lower cognitive load. Now, the map function isn't it though. I'd just avoid making it dotless, because it's not really any clearer, especially in a case where it's a higher order function, like in your example.
A technique can be understood, and still a pain in the ass to use; then it is bad. This is of course subjective, by comparison with other ways of solving the same problem.
Or, a technique can be useful, but too difficult to learn to justify using it on something simple; then it is inappropriate, and should be replaced by simpler, presumably less concise, code. This is of course subjective, dependent on the code's audience (readers, reviewers, maintainers). This is the root of the article's map vs for-loop example: different levels of familiarity with the technique of mapping functions over something.
On first pass I'm having issues imagining a specific function that would be part of a standard library that is sufficiently magic that it warrants avoiding. Learning is just a part of programming. Something that initially seems like magic can quickly becomes one of your favorite hammers and make you more productive (just found reduce-kv the other day, it's awesome). If it's in the standard library then at least being able to use it seems wise. Note I'm not saying one should or could reasonably be demanded to know everything about the standard library, but rather that being asked to know something in it going forward from a point is likely reasonable.
Magic tends to live in side-effect heavy libraries and frameworks. Rails is full of magic that can make you very productive for specific tasks, but learning all the magic takes a lot of time and the incantations are not necessarily generalizable so when you switch over to Django or Node you need to go learn new magic, or sometimes the magic your looking for doesn't exist anymore and you need to learn what was actually happening (this happened to me switching from Rails to Django, I forget what in particular but it was somewhat jarring realizing how much I had been taking for granted). Learning something like map will translate even if the literal syntax doesn't, knowing and relying on x-many separate opt-args for defining an ActiveRecord relationship to save explicitly typing a dozen lines might be magic not worth it especially if the whole team has to know.
Edit: Just wanted to add that although I took a shot at a rough distinction I want to clarify that I am not suggesting that I have the correct answer, I only hope to elicit the thoughts of better thinkers and posters.
The initial approach was to start the C# and IronRuby processes and have the IronRuby process watch the C# process for events, but this quickly broke down; the C# process ran in an STAThread, and in order to allow a second thread to interact with that it would have to be an MTAThread, and with 500K lines of existing code, there was no telling what changing that would do. So instead we inserted a callback in main. As soon as the CLR had loaded, if a command line parameter had been given, we started the IronRuby interpreter from the C# application. After hooking into some events we would drop out of the IronRuby interpreter and the C# application would start as normal, only going into IronRuby when an event handler was called.
There was just one problem. The IronRuby interpreter was starting in unmanaged code, and the CLR was seeing it as a noop. So when it got to the if statement that checked the command line parameter, it optimized the whole if statement out of existence.
My pair and I spent hours reading docs, trying to bend the optimizer to allow the if statement without hurting application performance. Finally, my pair inserted the following line into the if statement, after the call to the IronRuby interpreter:
Console.PrintLn("magic!");
It worked. The optimizer recognized the print statement as C# code and left the if statement alone.That's always what I think of when I think of magic in code.
map is one of those features. Closures are another.
The reason is simple. The cost of learning the feature gets amortized over all of the times you use it. It really isn't worth learning the feature just to see it used a single time. But if you've paid the price to learn it, then there is no reason not to use it wherever it makes sense.
Unfortunately there are some very large languages with many reasonable working sets of features, but sharp corners when you combine the wrong ones. C++ is notorious for this, but it is hardly the only one. In that case it is important to agree on the subset of features that you will use. And not introduce others willy nilly.
I would therefore appreciate the right to use map. But if my co-workers do not know it, I would think hard about how hard to press for adding it to what is needed to understand my code.
With maps, for instance, yes generally declarative programming is preferable to imperative programming (http://stackoverflow.com/questions/1784664/what-is-the-diffe...)
However, I've seen monster Java8 stream and C# LINQ statements that would benefit from additional intermediary variables, helper functions, and even cases where loops make things more clear.
You can't work in a vacuum on large projects. To be an effective engineer you have to understand the limitations of your team members. Forgoing map in lieu of a more verbose loop is completely appropriate if you're working with a bunch of junior people that need to maintain and fix bugs in the same codebase.
But good point poiting out to think hard about it when having co-workers working with your code, readability counts too !
The first time I encountered a program that used them in an interesting way, I spent 20 minutes of utter confusion trying to figure out where anything HAPPENED. When I finally found the magic line, I went to the person who wrote it and said, "Why didn't you at least leave a comment!??" He looked at me, slightly puzzled, and said, "What comment would have helped?"
Today these are much more widely understood than they used to be. Mostly because JavaScript forced people to learn it. Though at one point I did have a role in popularizing the concepts in the Perl community with http://www.perlmonks.org/?node_id=34786.
Yes, a good programmer will find out what is going on, they will be a better programmer there after. However alot of code is picked up by contractors or off shore, or even just by an intern and what happens then is that they hide their incompetence and introduce lots of bugs, or just fake the tests or lie about the tests.
If someone has proven to the compiler that a piece of code has a certain nonobvious behaviour, I'm usually intrigued.
If my interpreter starts parsing Roman numerals as constants because a gem depends on a gem which depends on gem `roman` (I'm looking at you Ruby), I'm not amused.
Scala's slick package is a fantastic example of advanced language features being used for good.
[1]: https://en.wikipedia.org/wiki/Source_Mage
[2]: https://en.wikipedia.org/wiki/Fast_inverse_square_root#Overv...
sorcerer.silverice.org
Why? Because magic exists, so save the word for when you see actual magic; don't waste the word on a metaphor?
val transformedCollection = collection map transformer
instead of val transformedCollection = collection.map(transformer)
?The dotless notation gets complicated easily the moment you try to extend it past one function call, as you start counting to figure out the order of operations. In fact, even using the shortcut of just using a function name instead of {x=> f(x)} or f(_) is seen as bad form in plenty of places, as knowing if a function has zero parameters, one m ten, or if it's varargs is very important information.
I'd limit dotless to very few cases, like symbolic operators, and very organic DSLs, and even then, it doesn't really work all the time: Just look at ScalaTest: Even Bill's wizardry doesn't let us say that "x should be 2": We have to write "x should be(2)", or "x shouldBe 2".
If it wasn't for symbolic operators, which are useful in some cases, I'd go as far as to not miss the existence of the feature at all.
I've moved to the dotless notation because I like that it provides a way to indicate pure code. Postfix operators are poorly supported though.
(xs map { x => x.key -> x }).toMapMost of the underlying details of the TCP, HTTP and SSL protocol stack are magic to me but it works pretty damn well - I'm really glad that I don't have to understand it fully in order to use it.
That ICA, FFT or MLP module is magic...
The module maybe complex but it is not magic. However it is something that I probably don't _need_ to understand to solve the problem I am currently working on.
Now I think about it, normally only very reliable code is called magic - the stuff that always goes wrong has some other choice descriptors.
This is what I tried to address. "Too abstracted" is far too subjective. Is "map" on a collection too abstract?
Dont get me wrong, code can be too abstract. But dont mistake too abstract for the unfamiliar.