The Crystal Programming Language Forum

Proposal: New "ends" keyword

In the real world, if you had any doubt on how to code it you’ll code it the way you know will work. And then test it. Right!

This proposal is separate from the endless method proposal (mine also). That one is strictly for oneliners, not for nested structures like here. This one is about reducing coding noise to increase conciseness and code brevity.

Why?

Care to explain the algorithm?

Oh, there’s a third way to interpret it:

class Foo
  def bar
    while true
ends

if foo
  while true
ends

Depends on how you set your parsing rules.

But again, you can already write syntactically ambiguous code.

But also of course, being the smart (non-masochistic) programmer, you’d never intentionally write ambiguous code you intended to use. But maybe if you’re a malicious hacker… But you can already do that now too.

Really, I think you’re getting the picture pretty good now.

Can you share an example of that?

From your example, there’s no context to evaluate if the if...end statement would be a dangling illegal snippet in that form, so depending on context, the compiler would flag it, or you would (of course) test your code to confirm it was working correctly.

I have to go out and live life now. I’ll get back with you later tonight|tomorrow.

Haha, yeah, my poor eyesight is the reason I prefer “end” over “}”. it’s just easier to identify than a single character.

1 Like

Visually it is a good syntactic sugar and I think there is a way to call a strict rule of use (example: last open => first closed). It’s simple to understand.

But on the other hand it’s a big source of mistakes.

def render(truthy : Bool, show_msg : Bool = false)
  return "ok" if truthy
  thing do 
  puts "it can start to get blurry" if show_msg
  return "here?"
  -> do "where I am?"
ends

I think someone previously proposed introducing endd (two ends), enddd (three ends), etc :-)

Ok, here are 2 simple rules that control (can make more if necessary).

  1. an end statement can only resolve 1 open thing.
  2. an ends statement must resolve 2 or more open things.

Thus this

def render(scene, image, screenWidth, screenHeight)
  screenHeight.times do |y|
    screenWidth.times do |x|
      color = self.traceRay(....)
      r, g, b = Color.toDrawingColor(color)
      image.set(x, y, StumpyCore::RGBA.from_rgb(r, g, b))
    end 
  end 
end

could also be written as

def render(scene, image, screenWidth, screenHeight)
  screenHeight.times do |y|
    screenWidth.times do |x|
      color = self.traceRay(....)
      r, g, b = Color.toDrawingColor(color)
      image.set(x, y, StumpyCore::RGBA.from_rgb(r, g, b))
  ends 
end

or

def render(scene, image, screenWidth, screenHeight)
  screenHeight.times do |y|
    screenWidth.times do |x|
      color = self.traceRay(....)
      r, g, b = Color.toDrawingColor(color)
      image.set(x, y, StumpyCore::RGBA.from_rgb(r, g, b))
    end 
ends

but not

def render(scene, image, screenWidth, screenHeight)
  screenHeight.times do |y|
    screenWidth.times do |x|
      color = self.traceRay(....)
      r, g, b = Color.toDrawingColor(color)
      image.set(x, y, StumpyCore::RGBA.from_rgb(r, g, b))
    ends
ends

For your example

class Foo
  def bar
    while true
    ends

    if foo
      while true
ends

you would then know to unambiguously write as this (if this is what you mean).

class Foo
  def bar
    while true
  ends

  if foo
    while true
  ends
end

Initially at least, the best practice will be to write it with standard format to get the program working correctly, then syntactically simplify afterwards.This would be no different than writing programs now. First make it work, then see how to refactor|rewrite to make it faster, more modular, etc afterwards.

Again, this would not be a coding requirement, but would be syntactical sugar.

As long as it is not required; personally, I like 1-1 end per start of block.

2 Likes

Yes, I think general best practice is start with encasing a single end for each class|module|method name thing, and use ends for internal structure. But then also use it for long strings of end statements, where it’s clear they are all the same termination point for multiple things (loops|conditionals).

The point here is to not make programming harder, but to make it easier|simpler. If you’re doing the former then you’re doing it incorrect. :slightly_smiling_face:

If ends is used for closing 2+ open “things”, then how would it know to not also close the class in addition to bar and the while loop?

Either way, this is a no from me. Adds a lot of complexity, overhead, and edge cases for essentially no benefit other than writing end a few times less.

5 Likes

There’s too much ambiguity in Crystal to allow something like this. while, if etc can be used as suffixes which throws the whole ends thing off. It also is something that clearly cares about indentation, something I never ever ever want to see be a thing in Crystal. Also issues with begin not needing to be explicit.

def test
  puts "Hello"
rescue 
  puts "Rescued"
end
1 Like

This still doesn’t resolve my trouble understanding how this is supposed to work. My understanding is that the parser will go through the code top to bottom, therefore when it gets to the ends in the first example, it won’t know how many end statements that ends is supposed to stand for. I suppose you could do preprocessing to find all the end and ends lines and then try to match them out-to-in with the beginnings of blocks/contexts, but I don’t expect that the parser already does that, so you’re adding a whole (non-trivial) parsing step for this one syntax addition.

I see, as a human, how those particular examples with just a method and some loops must be evaluated, but more complex examples become ambiguous, even for a human, and I don’t think the parser implementation to handle even the simple examples I quoted above will be particularly straightforward.

It’s really interesting how this one simple (I thought) proposal has produced
so much fear and anxiety. So let me try to lower peoples blood pressure.

1: You ARE NOT REQUIRED to ever use this.
If you don’t want to use this in your code then don’t! But understand it so you can read|understand code that uses it.

2: This is merely more syntactical sugar to write more concise|shorter code.

Crystal already provides shortcuts to simplify writing certain highly used patterns.

(x > 0) ? y : z

a if b

record Ray, start : Vector, dir : Vector
https://crystal-lang.org/api/1.0.0/toplevel.html#record(name,*properties)-macro

3: This DOES NOT create any whitespace dependencies.
Crystal doesn’t use whitespace|indentation dependencies now, and wouldn’t after.

4: The parser already does what this proposal wants.
No new fundamental process is required to implemented this. The parser already must identify and resolve endpoints. This probably adds a few more rules to accommodate it.

5: It can be implemented to fail for (non-trivial) ambiguities.
It can be implemented to produce error messages when it encounters ambiguities.
This will force coders to write proper legal code to do what they want|mean.

6: This won’t stop people from writing illegal code.
If you’re hellbent on writing illegal code, nothing will stop you now either, or afterwards.

(x > 0) ? y : z end

end a if b

record Ray, start : Vector, dir : Vector end

7: This does not change language internals.
This proposal has no affect on the internal implementation of Crystal, just source code formatting.

Ultimately, the only sure way to know the impact of this proposal is to implement it.
This shouldn’t be that hard, since Python|Nim do exactly this with whitespace and indentation. At minimum, those parsers provide a starting basis as a guide to implement this.

Finally, the Crystal devs are very bright people. I’m sure if they want it they can do it. The only question to me is if they feel like it’s worth doing. Of course I think it is, while some others expressed not.

But it’s really not an insurmountable technical issue.

No, but someone will, then in order to understand their code you’d have to learn it.

Given the amount of confusion around when/how it would know to stop I’m not so sure more concise|shorter code is the right word. Shorter maybe by a few lines, but deff not more concise.

Granted I’m not familiar with the parser, but I can’t imagine this being easier than the current implementation.

At the moment it knows that class or def will have a related end keyword. As a result it knows to look for this value after the body, and raise an error if it’s not there.

With this change, that would no longer be true as the parser would have to keep track of what was opened, what has been closed via a specific end keyword, and what thing(s) are closed by an ends when it finds one. In additional there would also prob have to be some pretty complex logic to prevent

class Foo
  def bar
    while true
  ends

  if foo
    while true
  ends
end

from not being parsed as:

class Foo
  def bar
    while true
    end
  end
end

  if foo
    while true
    end
  end
end # Error extra end

I’m sure it could be done but the question is should it. At this point, I’m pretty sure you’re the only one who thinks it would be :stuck_out_tongue:.

Could always open a PR to implement it then we can go from there :wink:.

2 Likes

Woops, fat-fingered something I didn’t want to send

Please only as a learning exercise.
PRs should not be sent if there is no preliminary consensus.

I predict that the overall reception is that the language’s design has absolutely no space for such a proposal.

I second what has been said - this doesn’t make anything simpler, only maybe quicker to write. But code is about reading, not about writing.

I also really disliked the presumptuous tone that people are just too lazy to embrace this amazing innovation.

8 Likes

I was thinking this same thing as I was reading over this. Using the ends instead of the current syntax makes it a lot less readable. Even if code could be written both ways it would hamper readability for a lot of people.

2 Likes