Closed asterite closed 8 years ago
I'm all in for removing them. We probably should add class_property
and related macros at the same time though.
I personally favor 2. Yeah, globals are evil, but:
I wish I had more experience of programming in crystal before trying to compare the choices. After the little bit of crystal programming that I've done so far, I'm a little surprised at how often I resort to global variables in the early stages of writing a program.
To what extent are "global" variables tied to modules? Would it help (at all) to say that user-defined $-variables are tied to a module, such that the programmer has to define a module to use them? And then to say that $-variables are something like @@class-variables, but at a module level?
( After thinking about it some more, I realize this was a dumb question. Please ignore! )
And then to continue with the idea of global-variables being tied to modules, you could then say that global variables would be defined after the module Whatever
line, the same way that class-variables are defined after the class Whatever
line:
module Curses
# <- global variables would be defined here ->
class Window
end
end
@drosehn I meant to say that this is uncommon:
puts Foo.var # <--- this, here, before even declaring Foo
class Foo
@@var = 1
def self.var
@@var
end
end
and in fact in Ruby there's no way to do that.
Could you show some code that you've written where you used global variables? I'd like to see some real use for them, other than for short scripts.
Then, global variables tied to a module are what class variables are. That's why we are proposing removing global variables in favor of class variables.
Finally, searching for "Ruby global variables" gives some very interesting results, with some quotes I will include:
I mean, if we can have a language where a thing that's strongly discouraged in the language from which it was inspired, I think that's a good idea, right? We try to do that with everything we want to improve over Ruby.
I'm sorry to say that I'm unprepared for a good discussion of this, and yet I do think it's a very significant issue and thus I think it deserves a good discussion. My gut feeling is that it would be nice to do away with truly global variables, but I do seem to use global variables more than I realized.
When I'm writing a program, I'm usually starting out under some time-pressure and with no time to sketch out the whole program before I start. That isn't the way I want to work, but it's the nature of the job I have. Due to that, I often find myself with classes which weren't designed quite right, and thus I need global variables to do the very thing that I should not be doing. Which is to say, I use them to get some rarely-changing information from one class to some other class.
So for that situation, it's convenient to have variables that are not tied to a specific class, but they don't have to be truly global. Can I use @@-variables which are defined in a module, but outside of any specific class? My first attempt to do that did not work out quite right, but it is pretty likely that I'm just doing something wrong.
In some other discussion about this topic I mentioned that I frequently take advantage of global variables when I start writing some new program, because I don't really know what I'm doing yet. But once I have a better understanding of what I need to write, then those global variables disappear, and are replaced by class-variables or other methods.
And do to that, the programs where I am using global variables are almost certainly ones which I wouldn't want to show to anyone else. I already know the program is a disorganized mess, so there's nothing there I would want to show off! :)
And while I'm admitting my ignorance, I'll also note that I don't know what @jhass means by
adding
class_property
and related macros.
What features would those macros provide?
module MyGlobals
class_property kill_all_humans = false
class_setter answer : Int32
class_getter! question : String
class_property? alive : Bool = true
end
would be the same as
module MyGlobals
@@kill_all_humans = false
def self.kill_all_humans
@@kill_all_humans
end
def self.kill_all_humans=(value)
@@kill_all_humans = value
end
@@answer : Int32
def self.answer=(value : Int32)
@@answer = value
end
@@question : String?
def self.question
@@question.not_nil!
end
def self.question?
@@question
end
@@alive : Bool? = true
def self.alive?
@@alive
end
def self.alive=(value : Bool)
@@alive = value
end
end
I'm all in for removing global variables 👍
I know it's not a direct comparison but for example Java also doesn't have global variables.
@sdogruyol lol, I said exactly the same thing yesterday to @waj
I think you can safely get rid of global variables. It's a fairly small shift to move from using a global reference to a class level reference.
👍 for removing global variables!
@jhass : Thanks. Now that I see what you mean, it seems very obvious! Yes, I'd like to see those.
Now that I've had more time to think about it (and to get some sleep!), I think it's best 👍 to drop global variables for crystal programs. I would list all my thoughts on this, but I think I've already written more than enough for one topic. :)
Last night I had trouble getting a @@class-variable to work at a module level, but I started with a fresh new program today and they're working fine for me. I must have been doing something wrong last night, and was too tired to see it.
I was going to favor solution #2, but after some thought on the subject, I'm inclined to vote for removal. There are available rather convenient ways to go without them, like constants, class/module properties or singleton pattern to name the few, so removing them wouldn't leave programmer without other sensible options to replace 'em.
I'm very much in favour of option 1, creating a singleton to run your program in, or using a module for constants is an easy enough workaround for hacky scripts, and has benefits.
I think the easiest tendency is to say "globals are evil - burn'em to death!", and most people would agree in a discussion.
I think this issue should be all about making a good case for them. Not every language have globals, but a great amount of them do. That's not a reason to say "hey, they have'em, we should too", but it'd be useful to understand why it is that they do support globals.
Instead of "yay/nay", I'd like this issue to be more about "this language/framework uses globals for this use case - but Crystal could make blah instead".
Please note that this is about removing $globals
, not the possibility to have global state.
I definitely, definitely, vote for removing globals. Classvars are so much better for global state. As for the rest mentioned, Crystal does just as it should imo.
I don't see any reason to have global variables. And they cause diseased programs. Let's remove them.
Hello,
In my personal opinion, I'm in favor of complete removal of global variables, including the special cases $~
and $?
.
Bear with me for a minute: the special cases $~
(regular expression matching) and $?
(process exit state) are two elements that might be problematic to keep up as they are now and potentially with threading.
They require special conditions (by using thread-storage to ensure these values do not interfere similar usage on other threads).
Why not drop them entirely? If someone wants to capture the MatchData
of Regex, simply use #match
and done.
If someone wants to capture the status of a process, simply use Process.run
.
By entirely remove references to $whatever
, it removes these Perl-isms (or Rubysm, whatever are you coming from) and keep things consistent and simplifies Crystal codebase at the same time.
On all the years that I have used Ruby, only once I have relied on $?
after using backticks to run a process and was simply to capture the output on a quick script. The more complex the script and the subprocess got, I was forced to drop the naive approach (unbuffered, non-streaming) and properly use pipes and redirection, thus ending there the life of my $?
usage.
But again, this is just my personal opinion.
If you reached this point, thank you for your time.
Cheers!
$?
and $~
are already thread local by being stack local, they're set in the parent stack frame directly.
@jhass correct, but they require special code to handle that. Keep the code to handle only these two special cases seems overly complicated (again, my personal opinion).
Well, see #1395 for my opinion on that ;)
I vote remove globals
Coming from Java to Ruby to Node.js to Elixir to Node.js again and now trying out Crystal. (Yes the concept of Node.js is cool, but I miss the Ruby syntax). The mutable global shared state is a huge problem almost everywhere. Elixir uses immutable data; it can only be re-bound. All consumers who accessed the value before it was rebound won't see the new value. No locking, No leaking; problem solved.
I agree on removing global variables, too
Remove globals! 💯 🌠
Guys, I'm sorry but how then to pass a variable or constant to scope of method/function? I have multiple variables outside method body and have undefined local variable error
@idchlife I think the best place to ask this question is in Google Groups: https://groups.google.com/forum/#!forum/crystal-lang
When you do, please provide a code snippet to help us understand what you mean and the actual use case you're trying to tackle so we don't end up with an http://meta.stackexchange.com/questions/66377/what-is-the-xy-problem
:)
@mverzilli Thank you! I tried stack, but it was ghosttown with crystal-lang
Yeah, for a more chat based experience head to https://gitter.im/crystal-lang/crystal or Crystal's IRC (they're bridged, so it's basically the same): https://crystal-lang.org/community/#gitterirc
Today we had a big discussion in Manas about what to do with global variables.
This will be long, but it's necessary to understand the problem.
First, let's talk about class variables. What does the following program print, to you?
You probably guessed one of these:
nil
@@var
must beInt32
, notInt32 | Nil
The current behaviour is the last one: it prints 1.
The reason for this is that it's very hard (maybe impossible) to do these two things at the same time:
An example to understand why this is difficult is this one:
A first compiler pass declares all types, methods, and the type and initializers of class and instance variables.
A later pass types the "main" program, which in this case is
Holder.new(Parent.new).foo
. When the compiler types this it sees that@parent.foo
is invoked, and@parent
is of typeParent
or any of its subclasses, so bothParent#foo
andChild#foo
are typed. When typingChild#foo
we see that@@const
is used, but wait, this happened before we had a chance to initialize@@const = 1
... so should an error be given? We did that in the past, and it brought a lot of false positive cases, like the above one. No,@@const
isn't really used before it was defined in the above example.However, if we replace "main" with this:
Now yes, it's used before it was defined. The problem is that the compiler can't distinguish both cases: it will always type
@parent.foo
with@parent
beingParent
or any of its subclasses.One possible solution, and the one we are currently doing, is to lazily initialize the class variable
@@foo
. The line@@foo = 1
is actually instructing the compiler to generate an initializer for the class variable. This initializer is lazily executed: either the first time when it's accessed, or when the compiler reaches the line@@foo = 1
when generating code (when traversing the syntax tree).This brings us back to why the original example printed 1. In a way, we think this is not that bad:
@@foo = 1
really looks like a class variable declaration, and so using it in a few lines above is just using the "initializer" value. As a side note, I think it's very uncommon to find such code, and one wouldn't write it on purpose, but it frees one from having to be strict about declaration order, and forward declarations are never needed.(This is actually true for constants, they work the same way (the difference is that constants can't be reassigned))
Again, trying to detect if a class variable or constant is used before it was declared, while doing hoisting at the same time, is very complex, and in fact I can't find an existing (compiled) language that does that. So the current behaviour is mostly correct and intuitive, and frees the programmer from a lot of problems.
Now, on to global variables. The problem with them is that there's no syntax to declare them. You just have
$x = 1
, which can either be the first time they are assigned (thus declared) or an update to them. With class variables it's more obvious: either the assignment happens at the class level, so it's a declaration, or it happens inside a method, so it's an update (yes, one can update a class variable at the class level, but I can't see a use case for that, and it's probably not an important case to consider).I don't want to extend this more, so we concluded we have three choices:
1. Remove global variables
We remove global variables from the language. You can use class variables or constants as a replacement. The variables
$~
and$?
stay, as they aren't really global (they are method-local).2. Keep global variables, but introduce syntax to declare them
It will be:
They will work very similar to class variables: they have to have a declaration, and it happens lazily, or they can omit the declaration in which case the type is inferred from assignments to them, but they will be nilable.
3. Keep global variables, consider the first assignment to them as the initializer
So this:
will print "Hello", then 1, then 1 again (the initializer is run either the first time the global variable is used).
Choice
My personal option for this is to remove global variables. In my opinion their usefulness is limited and they can always be replaced with class variables and constants. Adding special syntax just for global variables doesn't seem right. But of course we'd really like to know your opinion! :-)
(Also see https://github.com/crystal-lang/crystal/issues/1395, though I wouldn't personally introduce more special
$...
variables)