What are we talking about? Of course, the use of syntactic sugar does not lead to syntactic diabetes, but it can prevent you from thinking. This may sound strange, given that syntactic sugar is designed to make life easier for us: to wrap operations with abstractions into intuitive wrappers, to make programs easy to read, and just pretty. However, any instrument that directs our thought simultaneously keeps it in this direction.
The situation here is like a joke about roads, in which an American boasts:
- When I go on the road, I put a glass of water on the hood and drive at a speed of 100 km / hour. Not a drop of water spills.
What Russian says:
- And we all climb into the back seat and cut ourselves all the way into the maps, and we drink beer.
- And who drives the car?! ..
- Yes, where is she, to hell, get out of the rut.
It's funny, but, however, it is convenient, I would even envy those guys in the backseat if I did not have syntactic sugar. The track is good because it allows you not to think about how you are going, syntactic sugar allows you not to think about how you write, freeing the brain to concentrate on the task. But what if to solve the problem I need to do something strange? Move off the rut? The problem is not even that it is difficult to get out of it, it is that it is a rut for our thoughts - it just does not occur to us that there are other ways to do business.
But enough theory, it's time to mess your hands. I will give an example. Suppose we are writing an ORM and at a certain point we needed to get a list of the names of the fields of the model in order, for example, to make an SQL query. Also, suppose that fields are objects that have a name property containing a field name. Just something delov! You just have to run through all the fields, pull out the name and make a list:
var names = [];
for ( var i = 0 , l = fields.length; i < l; i ++ ) {
names.push(fields[i].name);
}
It looks like we are writing ORM in javascript, well, nothing, people do much stranger things these days. And by the way, we fell into the first syntax trap - “run” was interpreted as a cycle, and as a result we got a couple of variables, a comparison of integers, an increment and square brackets. Hell! This is not what I meant when I said "run"! Let's try again:
var names = fields.map( function (field) {
return field.name;
});
In short, nicer and much closer to the way I described the algorithm: run through the fields - fields.map, get the name - a function that gets the name. N-yes, function, how to get rid of it? Easy, let's not restrain ourselves, rewrite everything in python:
names = [field . name for field in fields]
One line! It would seem that we have reached the ideal, but in fact this option is in a sense even worse than the previous one: it is difficult to expand and it is almost impossible to reuse. Yah? Let's try, suppose we suddenly need to sometimes assign an alias of the table before the field names:
names = [ " %s . %s " % (alias, field . name) if alias
else field . name for field in fields]
It doesn't look so good anymore, but what if we need to assign an alias for the field? And after all it is required, it is not so something else. Well, let's not wait when our code goes out of control, refactor it preventively:
if alias:
names = [ " %s . %s " % (alias, field . name) for field in fields]
else :
names = [field . name for field in fields]
Clarity returned, but there was duplication. It is not by chance that list expressions that we use bind a single run syntax and operation on a single element into a single syntactic structure. Not a problem, just one list expression should remain:
if alias:
get_name = lambda field: " %s . %s " % (alias, field . name)
else :
get_name = lambda field: field . name
names = map (get_name, fields)
... or none - as soon as we got rid of the entanglement of the run and the list expression operation became unnecessary. There is another interesting point here - we are back to what we had in javascript. That is, the absence in the language of such a sweet element as list expressions led to the writing of a more universal code. Isn't it a great example of “less is more”, comrades?
')
So, I “dealt” with the for loop and the list expressions, it's time to go further. Do you like to access the properties of the object through a dot? To me - very, this is brief (except for the object itself and the desired property, only a small dot is present) and expressive (even a person far from objects will easily understand what the salt is). It is so convenient that we don’t think about alternatives, and in the same python there are three ways to get an attribute of an object (direct access to __getattr__, etc. - a cheat that does not give anything fundamentally new):
obj . name
getattr (obj, "name" )
operator . attrgetter( "name" )(obj)
We are interested in the last, most terrible option, because it turns an attribute access operation into a function. The one that we emulate with lambda. If this were the only way to get an attribute, then we would immediately write universal, extensible and reusable code:
from operator import attrgetter
names = map (attrgetter( "name" ), fields)
It may seem that I propose to abandon the syntax - no, this is an important part of most modern languages, providing readability and expressiveness of the code. In the end, I also do not mind moderately sweeten the code. What I want to say is that it is important to see the essence of what you are doing behind the syntax, to be able to push the syntax back so that the code expresses the task and that the individual parts of the task fall on separate syntactic elements.
PS I do not write ORM in javascript.
PPS I do not write ORM and on python, although at times I delve into ORM Django.
PPPS The strange idea outlined here came to me while reading Practical Common Lisp. For those who do not know, a lisp program is a set of nested lists, each of which consists of “what to do” (the name of a function, operator or macro) and subsequent arguments, i.e. represents the syntax tree of itself. In other words, there is no syntax in Lisp. And oddly enough, it makes the program on it surprisingly flexible.
UPDATE. To answer most of the objections, I’ll come up on the other side. Note that map (), which I eventually use, is also a rather high level abstraction. In fact, the abstractions that I use can be built into a hierarchy:
C-style for + abstraction from indexing = for-in
for-in + return result at each iteration = map
map + lambda = list expression.
I start from a low level and reach the level of abstraction, which best expresses what I'm trying to do. And if I do not need to generalize, then here I should stop, but if I have to summarize, I have to remember that the list expression is just a map and lambda in one bottle or start duplicating code. If there are no list expressions in the language (as in js), then I will immediately get the generalized code, but it will be lower level. If I forget that the list expression can be broken, I will begin to duplicate the code.
Summing up:
1. The absence of a certain syntax in the language leads to writing more flexible code.
2. This more flexible code will be lower level.
The second fee for the first,
from operator import attrgetter
names = map (attrgetter( "name" ), fields)
- premature generalization, if we have a point.