If we use tabs, then we can just have whatever tab stop we personally want, and nobody else is the wiser.
If we use tabs, then we can just have whatever tab stop we personally want, and nobody else is the wiser.
struct foo { /* nicely aligned
int x; * comment or whatever
int y; * designed for two-space tab */
};
struct foo { /* nicely aligned
int x; * comment or whatever
int y; * designed for two space tab */
};
Only the x and y lines moved when the tab size changed, so the comment is screwed.This is a nonstarter. If you use tabs, assume 8, or don't use tabs.
If you want flexible tabs, you must rigidly adhere to formatting rules which ban internal alignment between lines that are at different indentation levels.
Problem with this banning is that whereas it reasonably applies to comments like what I showed, those comments are a strawman example of internal alignment. Here is a less of a strawman example:
{
function(arg, arg,
arg, arg);
}
OK, this will work if we use exactly one tab for both lines, and then spaces for aligning the arguments: {
[Tab]function(arg, arg,
[Tab] arg, arg);
}
I'm not sure how good the editor support is for this; on a team of 15, you will easily find two who will mess this up often enough that the team's adjustable tab dream will crumble.If you want code that looks good with different tab sizes, that's a goal. Goals have to be verified and enforced, or they fall apart. You need a well-written commit filter which detects situations where moving tabs will wreck apparent alignment, or else team members need to waste their time policing this in reviews.
The most productive thing is to just ban tabs. The code looks one way, modulo your local font setting, and that's it.
If you forbid the mixed leading tabs and spaces, while insisting on tabs for indentation, then you're ruling out code formatting styles which exhibit such alignment.
That's a worse position that banishing tabs, because you are dictating code formatting ideologies for the sake of your indentation method.
The situation of tabs being banished does not, by itself, dictate to what the code should look like.
>The most productive thing is to just ban tabs.
There are valid accessibility reasons to prefer tabs to spaces.
Just because code is indented with character 32 rather than 9 doesn't mean that this indentation cannot be presented in an adjustable way.
Run things through a linter.
And in the function example I assume you do it because the args don't 'fit' on one line but then why not just have your editor wrap the line (and optionally indent by one level)? To spot the function name better? Why not use syntax highlighting for that?
And even then just use tabs for indentation and spaces for alignment, what would be the reason to force spaces on everyone else and make them suffer from an indentation width they don't like?
Alignment is desired because it enhances readability. All good visual design incorporates elements of alignment, grouping, separation and so on.
> why not just have your editor wrap the line
We could write function calls, no matter how many arguments they have or how large the expressions, on one line. That will result in an excessive line length. Generally, you want to avoid code wider than about 100 columns.
You may find yourself working with a prescribed coding convention which imposes such a constraint.
If you rely on an editor wrapping the line in a clever way, that's good for you. But you don't now how other tools will react to the long lines. E.g. someone wanting a print out of the code may find that the lines are getting truncated.
> what would be the reason to force spaces on everyone else and make them suffer from an indentation width they don't like?
Using tabs for indentation and spaces for alignment requires extra care, and a carefully tuned editor setup which understands the semantic difference between the tabs and the spaces which follow. In any sizeable team, deviations in formatting will creep in. Enforcing it will just be a big waste of time, compared to the simplicity of banishing tabs.
People who suffer from an indentation width they don't like have a psychological problem that perhaps makes them unsuitable for this line of work. The professional developer can happily work with any indentation format and maintain it as-is.
The only halfway valid argument here is regarding people with severe visual disabilities. Everything else is whiny primadonna fluff.
Tabs are still not the right tool here; accessibility has to be solved in such a way that the visually disabled developer is able to work equally well with any code that is thrown their way, tabs or spaces. You don't get to work on nothing but code that was developed in-house with accessibility in mind. Third-party code happens.
Also, you are overlooking that indentation isn't necessarily aligned to an absolute grid of tab stops. Nested indentation can start at a misaligned position.
(let ((fun (lambda (arg)
(indented-two-spaces))))
(also-two-spaces))
The indentation within the lambda is relative to the (lambda ... form in the previous line. If we change the variable name from fun to func, the entire lambda moves to the right by one character. (let ((func (lambda (arg)
(indented-two-spaces))))
(also-two-spaces))
"Tabs for indentation" is not a generally valid principle.It could work if tabs actually expanded to a fixed number of spaces, rather than advancing to an aligned tab stop.
(let ((fun (lambda (arg)
[TAB](indented-by-simply-expanding-tab))))
[TAB](also-indented-by-tab with
[TAB] split arguments))
Now suppose the whole thing above is at some nesting level: [TAB](let ((fun (lambda (arg)
[TAB] [TAB](indented-by-naive-tab))))
[TAB][TAB](also-indented-by-tab with
[TAB][TAB] split arguments))
As you can see, we need a more general mixture of tabs and spaces to have resizeable tabs such that alignment is preserved. Furthermore, tabs have to just expand to a given number of spaces (acting as a kind of variable-width "superspace"), rather than forward to the next aligned tab stop. (I don't think my editor can do this and don't know of any which can). The code will not look right except on such an editor.While that's true I was asking specifically about those examples of yours. What would be the reason to have a weird L shaped comment around the code like that? Right now I can only see two situations in which something is aligned in code, the first one being something like this
int foo;
double a;
because for some reason people like to have variables like that. Personally I don't see why anyone would want to do that but if they want to insert extra spaces they can do that, it's trivial to clang format before the commit and absolutely nobody even has the idea of using tabs there.The second one are function arguments over multiple lines. Personally again I find it a bit ridiculous that someone absolutely needs to have
void foo(arg, arg
arg, arg)
{
...
}
when void foo(
arg, arg,
arg, arg)
{
...
}
is just as readable and even saves you keystrokes because you can use an indent level for the alignment of the arguments and just have one tab (well two, since it's two lines) there instead of the 9 spaces you otherwise need. But again if they prefer the other style then that's fine, but it's no reason to force them to do the indentation with spaces too. Your example is flawed, you should never mix tabs and spaces like that, tabs never follow spaces. You use tabs to indent to whatever level you're currently on and then if you really want that alignment for whatever reasons you use spaces for the rest.> Enforcing it will just be a big waste of time, compared to the simplicity of banishing tabs.
I don't think a clang format commit hook is that hard to enforce. Not to mention that you are enforcing formatting anyway, otherwise you end up with one person using 2 spaces and the other 8. And if you already enforce that then there is no reason to not just enforce using tabs for indentation since they are objectively superior.
> People who suffer from an indentation width they don't like have a psychological problem that perhaps makes them unsuitable for this line of work. The professional developer can happily work with any indentation format and maintain it as-is.
> The only halfway valid argument here is regarding people with severe visual disabilities. Everything else is whiny primadonna fluff.
Do you really think that dismissing people's preferences as psychological disorders is a good idea? Especially in a community that likes to customize the shit out of everything? Why shouldn't they be allowed to see the code however they like? That's literally the idea of using tabs, you can see them as 37 spaces if you like but for me it's 4. If you want to do any extra alignment after the indentation then use spaces, sure. But who are you to decide how far right on my screen the body of the loop appears? Tbh if you ask me I find your proposal straightup draconic, you rather ban tabs completely rather than using the available tooling and/or properly training your people to enable everyone to work as they see fit. Yeah sure most people can work with a 8 spaces codebase if forced to but that doesn't mean it's a good idea.
And since you brought up disabilities: I've worked with someone who had only one eye, he didn't do any indentation because otherwise he'd have to constantly move his head around. Set tabwidth to 0 in his editor and you don't need to do any reformatting on commits if you indent using tabs. Force spaces on everyone and he needs to do a reformat all the time.
> (I don't think my editor can do this and don't know of any which can).
I'm not sure I follow why this is necessary. They already act like that, the only tabs not being as wide as X spaces are the ones that follow a non-tab character and at that point you are no longer doing indentation anyway so a tab should not even exist there. Though I don't see a reason to not have this, should be a simple setting in any editor.
I just demonstrated one. The semantics of tabs is that they move the printing position to the next tab stop position, which is absolutely determined, rather than relatively.
This does not work for nested indentation in modern (or even not-so-modern) programming languages.
If we re-imagine the tab as simply being a macro which expands to a configurable number of spaces, then tabs could be objectively superior.
It makes sense to advocate for that, and I'm all for it; but I don't place a high probability on it happening. Though a simple idea, it would have to be in every editor, and other tools.
You almost want to re-purpose some other control character for this purpose and leave tab alone.
About:
void foo(arg, arg
arg, arg)
{
...
}
Note that editor support this, so the only keystrokes you need to enter this from scratch, for instance in Vim, are: void foo(arg, arg[Enter]arg, arg)[Enter]{[Enter]...[Enter]}
The important attribute here is that the parameter list, which is an independent element of the phrase structure of the language, is presented in such a way that a bounding rectangle can be imagined around it. That rectangle includes only tokens from that element, and not from any other element: +----------+
void foo|(arg, arg |
| arg, arg)|
+-----------+
{
...
}
Boxes help readability; that's why we use tables and grids for presenting information or laying out user interfaces.Ideally, this principle would be applied to every syntactic element.
Looking at complex examples of Lisp code, I can't find a single counter-example: every sub-expression can be boxed in a rectangle which includes only that expression and its constituents: modulo a few closing parens that are written out to the right: )))). A big example of this is given below.
Now in the descendants of C syntax, the predominant brace placements throw a monkey wrench into this idea. We usually cannot draw a bounding box around a multi-line compound statement or other braced element such that the braces are included, and no tokens of neighboring constructs are included.
About that other style, for consistency, I would want this:
void foo(
arg, arg,
arg, arg
)
{
...
}
if the opening parenthesis is not on the same line as the arguments, the closing one should not be either.I work in a shop where the style closely resembles this one, except the parameter declarations are on separate lines:
int put_bytes(
int fd, // comment
char *buffer, // comment
size_t size, // comment
)
{
}
Now I personally don't like this, but I can work with it, and can see the advantages. If you have to add, remove or reorder arguments you are not fiddling with the parentheses at all. When you take a unified diff of the edit, it does not include lines that only differ because of a parenthesis. The lines marked + or - are all "payload". The inner alignment of the parameter names is helpful, as is having the space for commenting the parameters.I still wouldn't do this in a side project where I dictate the coding convention, but I can respect that and work with it without grumbling.
Now about that bounding box thing, here is a random example I picked from the SBCL compiler sources. The file is:
https://github.com/sbcl/sbcl/blob/master/src/compiler/srctra...
The macro bound-binop, with some ASCII rectangles inserted, looks like this.
Only the closing parentheses at the end violate the principle: the rectangle includes more parentheses than are strictly part of that syntax, and this problem is minimized by closing the parentheses together on the same line.
I suspect that the programmers who work with Lisp and find it readable all grok this bounding box model, whether consciouosly or not.
(defmacro bound-binop (op x y)
+-----------------------------------------------------------------------------------------------+
| (with-unique-names (xb yb res) |
| +-------------------------------------------------------------------------------------------+ |
| | `(and ,x ,y | |
| | +-----------------------------------------------------------------------------------+ | |
| | | (with-float-traps-masked (:underflow :overflow :inexact :divide-by-zero) | | |
| | | +-------------------------------------------------------------------------------+ | | |
| | | | +-------------------------------------+ | | | |
| | | | (let* |((,xb (type-bound-number ,x)) | | | | |
| | | | | (,yb (type-bound-number ,y)) | | | | |
| | | | | (,res (safely-binop ,op ,xb ,yb))) | | | | |
| | | | +-------------------------------------+ | | | |
| | | | +---------------------------------------------------------------------------+ | | | |
| | | | | (set-bound ,res | | | | |
| | | | | +--------------------------------------------------------------+ | | | | |
| | | | | | +----------------------------+ | | | | | |
| | | | | | (and |(or (consp ,x) (consp ,y)) | | | | | | |
| | | | | | +----------------------------+ | | | | | |
| | | | | | ;; Open bounds can very easily be messed up | | | | | |
| | | | | | ;; by FP rounding, so take care here. | | | | | |
| | | | | | +-----------------------------------------------------+ | | | | | |
| | | | | | |,(ecase op | | | | | | |
| | | | | | | +-------------------------------------------------+ | | | | | | |
| | | | | | | | (sb-xc:* | | | | | | | |
| | | | | | | | ;; Multiplying a greater-than-zero with | | | | | | | |
| | | | | | | | ;; less than one can round to zero. | | | | | | | |
| | | | | | | | `(or (not (fp-zero-p ,res)) | | | | | | | |
| | | | | | | | (cond ((and (consp ,x) (fp-zero-p ,xb)) | | | | | | | |
| | | | | | | | (>= (abs ,yb) 1)) | | | | | | | |
| | | | | | | | ((and (consp ,y) (fp-zero-p ,yb)) | | | | | | | |
| | | | | | | | (>= (abs ,xb) 1))))) | | | | | | | |
| | | | | | | +-------------------------------------------------+ | | | | | | |
| | | | | | | | | | | | | |
| | | | | | | +-------------------------------------------------+ | | | | | | |
| | | | | | | | (sb-xc:/ | | | | | | | |
| | | | | | | | ;; Dividing a greater-than-zero with | | | | | | | |
| | | | | | | | ;; greater than one can round to zero. | | | | | | | |
| | | | | | | | `(or (not (fp-zero-p ,res)) | | | | | | | |
| | | | | | | | (cond ((and (consp ,x) (fp-zero-p ,xb)) | | | | | | | |
| | | | | | | | (<= (abs ,yb) 1)) | | | | | | | |
| | | | | | | | ((and (consp ,y) (fp-zero-p ,yb)) | | | | | | | |
| | | | | | | | (<= (abs ,xb) 1))))) | | | | | | | |
| | | | | | | +-------------------------------------------------+ | | | | | | |
| | | | | | | | | | | | | |
| | | | | | | +-------------------------------------------------+ | | | | | | |
| | | | | | | | ((sb-xc:+ sb-xc:-) | | | | | | | |
| | | | | | | | ;; Adding or subtracting greater-than-zero | | | | | | | |
| | | | | | | | ;; can end up with identity. | | | | | | | |
| | | | | | | | `(and (not (fp-zero-p ,xb)) | | | | | | | |
| | | | | | | | (not (fp-zero-p ,yb)))))))))))) | | | | | | | |
| | | | | | | +-------------------------------------------------+ | | | | | | |
| | | | | | +-----------------------------------------------------+ | | | | | |
| | | | | +--------------------------------------------------------------+ | | | | |
| | | | +---------------------------------------------------------------------------+ | | | |
| | | +-------------------------------------------------------------------------------+ | | |
| | +-----------------------------------------------------------------------------------+ | |
| +-------------------------------------------------------------------------------------------+ |
+-----------------------------------------------------------------------------------------------+ +--------------------------------------+
|namespace foo |
|{ |
| +------------------------------+ |
|[TAB]|void bar(args) | |
|[TAB]|{ | |
| | +-----------------+ | |
|[TAB]|[TAB]|while (condition)| | |
|[TAB]|[TAB]|{ | | |
|[TAB]|[TAB]|[TAB]// body | | |
|[TAB]|[TAB]|} | | |
| | +-----------------+ | |
| | | |
| | // and another scope | |
| | +-----------------+ | |
|[TAB]|[TAB]|if (condition) | | |
|[TAB]|[TAB]|{ | | |
|[TAB]|[TAB]|[TAB]// body | | |
|[TAB]|[TAB]|} | | |
| | +-----------------+ | |
|[TAB]|} | |
| +------------------------------+ |
|} |
+--------------------------------------+
Your problem appears when a new scope does not seeem to start at the beginning of a line, correct? For example like this: +--------------------------------------+
|namespace foo |
|{ |
| +------------------------------+ |
|[TAB]|auto bar(args) | |
| | // function body | |
|[TAB]|{ | |
|[TAB]|[TAB]return baz([args](){ | |
|[TAB]|[TAB][TAB]// lambda body | |
|[TAB]|[TAB]}); | |
|[TAB]|} | |
| +------------------------------+ |
|} |
+--------------------------------------+
If you try to draw a rectangle around only the lambda you have to "indent" the body by spaces to make it nicely aligned like this +--------------------------------------+
|namespace foo |
|{ |
| +------------------------------+ |
|[TAB]|auto bar(args) | |
|[TAB]|{ | |
| | +----------+ | |
|[TAB]|[TAB]return baz(|[args](){ | | |
|[TAB]|[TAB][TAB] | ... | | |
| |[TAB] |} | | |
| | +----------+ | |
| |[TAB]); | |
|[TAB]|} | |
| +------------------------------+ |
|} |
+--------------------------------------+
and someone misguided could have the really bad idea of first wanting to align a box and then doing the indent for the lambda scope: +--------------------------------------+
|namespace foo |
|{ |
| +------------------------------+ |
|[TAB]|auto bar(args) | |
|[TAB]|{ | |
| | +----------+ | |
|[TAB]|[TAB]return baz(|[args](){ | | |
|[TAB]|[TAB] |[TAB]... | | |
| |[TAB] |} | | |
| | +----------+ | |
| |[TAB]); | |
|[TAB]|} | |
| +------------------------------+ |
|} |
+--------------------------------------+
That obviously leads to problems, yes, but that is simply someone who did not understand "tabs for indent, spaces for alignment" then. You should train that person better, it's really not that hard and also there's tools that can easily warn on such things. Banning tabs gets rid of this problem but it also gets rid of the advantage tabs have, namely that the indentation can look differently to many people without causing any VCS problems. And that's worth a lot more than saving a one time 2 minute work of enforcing a check for such things in the VCS for a single person.The 1960's tab modeled after typewriter tab stops is not up to snuff to these requirements.
You fall back on spaces, and then you have a half-baked result where only some of the indentation scales with the tab size.
I suspect that a successful solution will not involve tabs at all. It's algorithmically possible for the text presentation tool, such as the editor, to infer which spaces are indentation and which are alignment, informed by the language syntax, and then allow the indentation spaces to have a configurable size.
I posited that if tabs had simple expansion semantics (tab produces fixed number of spaces starting at any position) that would also be workable. Unfortunately, it requires integration into way too many tools all at the same time.
If you want to use tabs for indentation then you absolutely must enforce that they are used consistently, and I've never seen any organisation succeed at that. Whereas if you use spaces then you can simply ban the tab character from all source files (which organisations do enforce), and then at least you guarantee that everyone will see the same thing when they look at a given piece of code.
Indent only using tabs, based only on scoped, and there's no possibility of problems. It's super simple to enforce via linting.
People who love column alignment are really painting their code on a grid of characters and expect to be able to paint any coordinate. There are even editors that let you click past the end of the line and they'll fill in all the spaces to that point. It's madness, I tells ya.
It's still indentation - the point is lining up the start of a line with some point on the line above.
> It's super simple to enforce via linting.
And yet I've never seen anywhere manage to actually enforce it in practice.
there's, like, a message in there about life and society.
Only if you are mixing tabs and spaces in a strange way. If you use tabs reasonably then it looks right to each user's preferences.
I think the reason that spaces are popular are that it is more obvious when done wrong. We should probably just have better code review tools that highlight when tabs are used for anything but initial indentation. It is as simple as /[^\t]\t/.
The other downside of tabs is that you don't have a well defined line width, but IMHO line limits are a fairly silly lint anyways, you should make the code look reasonable. I find that formatters based on line length limits tend to make code very awkward to read.
https://hoffa.medium.com/400-000-github-repositories-1-billi...
The only exceptions being C, where tabs are slightly more common, and Go, where tabs are the official style.