Redundancy of notation in instance_variable_set, instance_variable_get - ruby

When I set or get instance variables using some name, for example #foo, I can do something like:
instance_variable_set("#foo", some_value)
...
instance_variable_get("#foo")
But often, I use a variable for the method name, which does not include the # prefix, so that I end up doing:
method = :foo
...
instance_variable_set("##{method}", some_value)
...
instance_variable_get("##{method}")
But since all instance variables are prefixed with #, I think it redundant to have to type "##{method}" instead of simply typing method. Why are the methods instance_variable_set and instance_variable_get not designed to accept string/symbol without # as its first argument like this:
method = :foo
...
instance_variable_set(method, some_value)
...
instance_variable_get(method)
where the variable to be actually set will be #foo rather than foo?
Is there any advantage with the way it is?

The reason is, quite simply, that the instance variable is named #foo, not foo. The # is part of the variable name, just as the $ is part of the global variable name $foo.
The reason that # is not necessary when calling attr_accessor and friends is because they define attribute methods, so it makes sense to provide the method names, not the variable names.
Of course there is no technical reason instance_variable_set cannot prepend the # itself. However, the method accepts a symbol that corresponds to the variable name. A symbol by definition represents the identifier with the given name. So the only symbol that corresponds to the instance variable #foo is :#foo. That is why you have to include the #, because we know that :foo does not correspond to any instance variable identifier at all. (And if you supply a string, it will be converted to a symbol internally first.)
Update: In the C Ruby implementation (MRI), there is actually no mention of # anywhere in the code that handles instance variables. Only the parser knows instance variables start with a #. So it seems that separating code parsing from implementation is another possible reason.

Related

Why we are using _ prefix in some cases to define instance variables in ruby?

I see in ruby classes some people using _ prefix to define instance variables like #_property while in normal condition attr_writer uses the normal name like #property to define instance variables.
What is the point of doing this and what is the difference?
The _ prefix isn't a language feature, it doesn't do anything special, it is just a convention.
In most cases, the developer wants to indicate that a variable is not in use, for example, a block variable that is not used in a block. Or they want to indicate that the variable is an internal variable of a module or gem and that others should not read or modify this variable directly.
There are 2 possible answers here depending on type of a variable.
Instance variable
When _ is used to prefix an instances variable (like in your question) it is usually just a convention to make it clear that that instance variable is private implementation detail and should not be used outside of the current scope. You might encounter it especially in modules which are to be included in some other classes - in which case the instance variable is defined and used by that module, but it belongs and is scoped to the object itself. I personally prefer object-scoped registries for this, but "private" instance variable is a quick, dirty and popular way to go. Alos, prefixing instance variable name with _ reduces chances of name conflict.
# Library code
module SomeExternalLibraryModule
def foo
#_something ||= SomeExternalLibraryModule::Something.new(self)
end
end
# Application code
class User
include SomeExternalLibraryModule
attr_reader :something # name conflict "avoided"! Phew!
def bar
#_something.do_sth
# "_" means - you'd better know what you're doing here!
# There is no guarantee that instance variable will be present in the future release
end
end
Local variable
When local variable is prefixed with _ this means that that variable is not being used in the code. It is especially useful when using iterators, or consuming other multi-element inputs of which you're interested in only one.
It is quite common to see just _ as a variable name. It has a bit of a special meaning for parser, which is you explicitly saying "I do not care what that is". As such, it is the only argument name that is allowed multiple time in a single definition:
def foo(_, _, a)
a
end
foo(1,2,3) #=> 3
def bar(a,a,b); end #=> SyntaxError
However, this is usually the best practice to use "I do not care" with a name, which will make your life easier in the future if you actually decide that you need to use other arguments:
def foo(_name, _html_options, options)
options.delete(:some_option)
super
end

Iv'e tried defining a function in ruby with constant parameter names, why does it throw an error?

whenever I try doing def foo(CONST1, CONST2)..., it always gives an error, even if only the function name is a constant.
# Error!
def foo (BAR, BAZ)
puts BAR + BAZ
end
Ruby is an unusually case-sensitive language so you can't do certain things because capital letters have syntactical meaning. Other languages don't care, but Ruby does.
For example your method arguments must be variables. You cannot define constants here. Capital letters at the beginning indicate a constant, no exceptions.
You should define this as:
def foo(const1, const2)
# ..
end
Some rules:
Method names, variables and arguments are lower_case.
Classes and modules follow ClassName and ModuleName respectively. These are technically constants.
Other constants are defined as CONSTANT_NAME
I'm not sure what you're trying to achieve by defining "constant parameter names", so if you could explain maybe there's a better answer here.

In Ruby, what does a top-level assignment do?

I have the following code at the top-level of a .rb file:
class Times
def initialize(n)
#n = n
end
def each()
(1..#n).each {yield}
end
end
three_times = Times.new(3)
def f()
Times.new(3).each {puts 'Test'}
end
f()
This works, and prints 'Test' three times, as expected. However, if I replace Times.new(3) in f with three_times, i.e. three_times.each {puts 'Test'}, I get an error:
`f': undefined local variable or method `three_times' for main:Object (NameError)
Why does this not work? Why is Times accessible from within f, but not three_times?
More generally, what exactly does an assignment at the top level (e.g. three_times = Times.new(3)) do?
Because
three_times is a local variable
local variables are only accessible inside of a specific scope
def in ruby creates a new scope
So when f is invoked, it does not see or have access to three_times
To access three_times change it to either a global variable $three_times or an instance variable #three_times
The reason that you are able to reference the class Times is that it is a constant and ruby goes through a separate process of lookup for constants.
Sidestepping issue with def
You could also access the local variable by using a block to define your method, which sidesteps the whole scope gate issue. I do this sometimes when writing rake tasks but rarely do it outside of scripts.
three_times = Times.new(3)
define_method :foo do
three_times.each { puts 'Tests'}
end
foo
Why does this not work? Why is Times accessible from within f, but not three_times?
Variables whose name starts with a lowercase letter are local variables. Local variables are local to the scope they are defined in (that's why they are called local variables.)
Variables whose name starts with an uppercase letter are constants. Constants are looked up first in the default constant scope, then lexically outwards, then dynamically upwards by inheritance.
More generally, what exactly does an assignment at the top level (e.g. three_times = Times.new(3)) do?
Nothing special. It does the same thing that an assignment anywhere else does. In this case, it:
Dereferences the variable (constant) Times, let's call this object o1.
Evaluates the literal integer expression 3, let's call the resulting object o2.
Sends the message new to o1, passing o2 as an argument. Let's call the answer to that message send o3.
Binds o3 to the local variable named three_times.
As you can see, there's nothing in there that is somehow specific to script scope or the top-level.
It's because it's looking for a local variable called "three_times". If you wish to make "three_times" to be "top-level" or "global", prepend the variable name with $ so that it's "$three_times".
Your code works for me, there is no errors. You can call f() sucecsfully

coding style: use parameter or instance variable?

I often have functions which take a parameter, set an instance variable to that parameter, and then do other things, e.g.:
def updateFoo(self, foo):
self.foo = foo
fooProcessor1(foo)
fooProcessor2(self.foo)
Do you prefer to pass the parameter itself, as in fooProcessor1, or the newly-set instance variable, as in fooProcessor2? Why or why not?
A function named setFoo() really shouldn't do anything more than setting foo unless it is computing and caching a value derived from foo, in which case I would advise something along the lines of:
def setFoo(self, foo):
self.foo = foo
self.__fooUpdated()
def __fooUpdated(self):
# Recompute values derived from foo, dispatch signal to listeners, etc.
Of the options you suggested, I prefer fooProcessor1(foo). That said, it is mostly a matter of personal preference. As long as you are consistent, I don't think it matters all that much.
Coders should be lazy. self. is way too much to type at 1 am.

What is the real reason for Ruby class names being capitalised?

Ok, so everyone knows that capitalised identifiers are seen to be ‘constant’ in Ruby.
However, it is possible to assign a new value to constants, so the following works
class A
def self.hi; "hi"; end
end
class B
def self.hi; "ho"; end
end
A.hi # => "hi"
B.hi # => "ho"
A = B
# warning: already initialized constant A
# => B
A.hi # => "ho"
and also does assigning to a lowercase identifier work:
a = B
a.hi # => "ho"
However, the internal name is set to ‘B’ in all cases and the new identifier names are also only referencing ‘B’.
But if it’s technically okay for the identifier to have a lowercase name, why is this restriction on the internal name for the class? It can’t be that it needs to be constant, because it isn’t constant; so the other reason could be that lowercase identifiers have a different scope. But then: why not having a class with lowercase scope?
I’m wondering because the parser even allows for method names in unicode and such, so it seems strange that for classes it’s so much more restricted.
As you said, the name of a class is that of the first constant it has been assigned to. You can only use constants with the class keyword because it has been decided that the class keyword should only be used to create named classes.
The rationale probably being that allowing local variables would confuse uses, who would use class lowercase_class_name, not knowing the difference between lower case and upper case identifiers and then wonder why the classname was not visible everywhere (i.e. why def foo() lowercase_class_name.new end did not work).
By making the default way to create classes specific to constants, classes defined this way will be visible everywhere and there is at least a suggestion that the value associated with that name should not change (i.e. you get a warning otherwise).
People who know what they're doing can still do local_variable = Class.new { ... } if they need to.
The interpreter needs one way to find out if it is a Class name or a member variable of the current class/module.
The interpreter has to lookup class names in a global hash table while the member variables are in the class hash table. Local variables are different as they can be resolved by the interpreter without looking up hash tables at all (they have precedence over member variables).
And the only thing the interpreter knows about an identifier are the characters. Therefore there is the notational difference. Thats also the reason why global variables have a preceding $ and class variables an # character. For this the interpreter has other hash tables it has to search.
Other script languages have similar restrictions, for example PHP needs a dollar in front of each variable.

Resources