Syntax suggestion: end<control>


#1

end could be a synonym for end. so

if (a=b)
   continue
endif

would be equivalent to

if (a=b)
   continue
end

In the long-term, the compiler could check whether the control statement is aligned to its end, too. This can be a useful feature for finding bugs in a long program with a lot of control-nesting.

if this has already been discussed, or implemented (and I don’t know), please ignore this post.


#2

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()
           quote
               if $condition
                   $expression
               end
           end |> esc
       end
@if (macro with 1 method)

julia> x = 42
42

julia> @if x == 42 (
           info("yep!")
       ) endif
INFO: yep!

julia> @macroexpand @if x == 42 (
           info("yep!")
       ) endif
quote  # REPL[9], line 4:
    if x == 42 # REPL[9], line 5:
        info("yep!")
    end
end

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).

Also you could always do:

if (a=b)
   continue
end # if

:stuck_out_tongue:


#3

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.)


#4

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.


#5

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.


#6

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.


#7

ok, we can disagree on the benefit. it may rank from zero to medium. it is certainly not great.

but what is the cost here?

  • complexity increase of compiler? no.
  • complexity increase of user code? no.
  • learning curve? no.
  • pollution of namespace? endif, endwhile, endfor … hopefully not common. moreover, if someone used them, their code would probably be harder and not easier to understand.

what have I forgotten?


#8

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.


#9

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.


#10

then I was wrong.

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 :wink:

(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.)


#11

I think this says more about the naming of begin ... end blocks.
The naming is quite unspecific, begin what?

Perhaps:

block
   ...
endblock

Although I think ending all constructs with end is fine.


#12

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

ok, I have said my spiel. Up to the julia team…


#13

you don’t need endwhile and enddo to have nested blocks in one-liners:

julia> for i in 1:10 if isodd(i) println(i) end end
1
3
5
7
9

(not that I’d generally advise writing code like this)


#14

I do not need it. but if I want it (and I sometimes do!), it is a little more expressive:


#15

This can’t be done with a macro unless one uses different words for if, for, while, do, etc. Correct?


#16

AFAICT you are correct, the parser will complain that the expression is incomplete before it gets to the macro.


#17

It can be done, see my answer above:

julia> keywords = [:if, :for, :while, :do]
4-element Array{Symbol,1}:
 :if
 :for
 :while
 :do

julia> for keyword in keywords
           @eval macro $(keyword)(block, terminator::Symbol)
               terminator ≠ Symbol(:end, $(Meta.quot(keyword))) && error()
               esc(:($block))
           end
       end

julia> @do (
           println("My do-enddo block.")
       ) enddo
My do-enddo block.

julia> @for true endfor
true

julia> @while 1 + 1 endwhile
2

julia> @while 1 + 1 endfor
ERROR: LoadError:
Stacktrace:
 [1] error() at .\error.jl:44
 [2] @while(::LineNumberNode, ::Module, ::Any, ::Symbol) at .\REPL[24]:3
in expression starting at REPL[28]:1

Is that what you mean?


#18

What I meant is that I don’t think you can do

@some_macro for ... endfor

because it will not be parsed without an end. Of course if you replace for, this is not a constraint.