You could do something like this, just as a proof of concept (not taking elseif/else branches into account), similar macros could be done for the other type of blocks:
julia> @eval macro $(:if)(condition, expression, terminator::Symbol)
terminator ≠ :endif && error()
end |> esc
@if (macro with 1 method)
julia> x = 42
julia> @if x == 42 (
julia> @macroexpand @if x == 42 (
quote # REPL, line 4:
if x == 42 # REPL, line 5:
But then again in Julia a lot of control nesting can become a lot of multiple dispatch instead, I would refactor such a long if block. In Julia you can have pretty much any syntax you like and in a package.
I just wish there where some kind of macro line continuation syntax or semantics, but the parenthesis do the job here (instead of a begin ... end, which would defeat the purpose of the trailing endif).
It would be fairly straightforward to write a short function that parses a julia file and throws an error message whenever end#if closes a block that was not opened by an if, etc. Then let runtests.jl run this on every file in your package before they are loaded.
(The parse() function does not seem to keep track of comments, but it might work to search-replace end#if to end;Expr(:meta,:if); before parsing, and then search the AST for this pattern.)
Perhaps the Lint.jl authors could be convinced to add this as an option.
But I am skeptical of the utility of this feature. And end which is very far from its opening pair already indicates code smell and suggests refactoring. Same applies to unclosed opening constructs (for, begin, let, function, …) and similar stuff. Emacs immediately starts to passive-aggressively misindent everything once I forget to match an opening, so I rarely ever run into making this kind of error.
This is a good recommendation for function and control block but it is certainly not true for types def with many constructors or (nested) modules. clang format provide similar function for namespace name.
I would suspect the need for many inner constructors too; or long implementations for those constructors—if complicated calculations or checks are needed, they should be refactored into a separate function, or possible multiple small functions.
I have to admit though that lately my programming style has been influenced a lot by Robert C. Martin’s “Clean Code” and related books, and tend to write much shorter functions. I recognize that this is not the style favored by all Base contributors.
Perhaps a concrete example to discuss would be interesting.
At the risk of being too presumptive, (because there certainly are pieces of code out there that are just really complicated and hard to reorganize in a more succinct way) I find that usually if your code contains so many nested blocks that you can’t keep track of where they close it is probably a sign that you should reorganize your code into separate functions.
What’s the cost? I think I’d disagree with just about every one of your items on that bulleted list — sure, none of them are huge costs, but they are definitely costs none-the-less.
The biggest cost, though? Developer time. Not only would this cost the time to implement it in base Julia itself, but it’d also require every single Julia grammar file to be updated for proper highlighting and indentation. And then there’d be the “forever” cost of folks who believe that this is the “better” way of spelling end slowly converting ends to endifs, endwhiles, and endbegins because “it’s safer” throughout the ecosystem.
Plus that last one, endbegin would just be bizarre.
I judged the cost to be smaller than the benefit. I am not a developer; I thought it would just be updating a few tables—an hour for the alias. (not yet checking proper closing.) . about the same time as discussing it
(the syntax highlighting and indentation is a reason why I preferred it in the core language. and it is a little safer, indeed, though trivially so. hope most would recognize it. endbegin indeed seems bizarre, but then end would still work just the same and seem more natural.)
now that I have spent another month in Julia, I think my original suggestion was not so bad, after all. See, we already have many functions aliased, or near-aliased (such as readcsv instead of readdlm). Aliasing (without checking) a couple of endvariants to end is not against the spirit of many other existing aliases; and some authors (=me) like the mnemonic. If we had a C-like preprocessor, I could do this myself:
#define endfor end
#define endwhile end
#define endfunction end
#define endlet end
#define enddo end
#define endif end
matt: with respect, I disagree that this is more harmful than the dozens of other function aliases that we have. Anyone who likes a pure end can just use it. Up to them. It’s just a pure alias. (if julialint eventually wants to check for matching, up to julialint.) and although I do not know the julia internals, I would hope that these aliases would be easy to code.
it then allows one-liners that one could not otherwise one-line with the end#while construct:
julia> open("/etc/passwd","r") do fout; while (!eof(fout)); endwhile; enddo