crystal-lang / crystal

The Crystal Programming Language
https://crystal-lang.org
Apache License 2.0
19.46k stars 1.62k forks source link

Scope method #15185

Open BigBoyBarney opened 1 day ago

BigBoyBarney commented 1 day ago

From a discussion on Discord with StraightShoota about scoped variables. I think it could be useful to be able to limit the scope of a variable in a syntactically sound manner, for example:

def scope(&)
  yield
end

scope do
  x = 1
end

x # Error: undefined local variable or method 'x' for top-level

Current workaround would be something like

loop do
  x = 1
  break
end

1.times do
  x = 1
end
straight-shoota commented 1 day ago

We'll need to look at actual use cases.

I have some doubts about general usefulness. Scope limitations are mostly relevant for looping, and all constructs except while already implictly create a scope. So the only use case I can imagine is inside a while loop.

while condition
  scope do
    body
  end
end

But if you need to loop with scoping, there are already plenty of alternatives, like loop. And it's even more concise IMO:

loop do
  break unless condition
  body
end

If there are any other use cases for explicit scoping, let's hear them.

BigBoyBarney commented 1 day ago

Intermediate calculations requiring a few variables, but where only the result matters, could be such a use-case IMO, where you wouldn't need to clutter the namespace with variables. Of course, you could extract the actual logic into a method and have the variables scoped that way, but I think the syntax would fit in very well with the rest of the stdlib, and you wouldn't need to create a separate method for a one-off call.

I can't really think of other use-cases off the top of my head, but I also don't see a reason why it wouldn't be useful to have, as it doesn't directly overlap with anything and is very idiomatic.

straight-shoota commented 1 day ago

The rationale for adding anything to stdlib must be that it provides reasonably useful value. Unless we can be sure about that, it's not going to happen.

BigBoyBarney commented 1 day ago

As I mentioned in my previous comment, scoping variables outside of loops is reasonably useful.

An example from AnimeGameTools

y = "'y'".colorize(:green)
yes = "'yes'".colorize(:green)
n = "'n'".colorize(:red)
no = "'no'".colorize(:red)
print "Would you like to generate new keys? (#{y}/#{n}): "
choice = gets.try &.strip.downcase
until ["y", "n"].includes? choice
  print "Please enter either #{y} for #{yes}, or #{n} for #{no}: "
  choice = gets
end

Scoping this wouldn't clutter the namespace with the variables.

straight-shoota commented 1 day ago

I'm not sure I understand the issue with cluttering the namespace. What is the negative practical effect with that?

And is it not possible to extract isolated portions into private defs? I believe this is usually a preferred solution because it labels the purpose and defines a clear interface of inputs and outputs.