Understanding Ruby Nested Functions - ruby

I am learning ruby at the moment. I am trying to understand the way closures work, and how they are different from functions. I am fully aware that closures should be implemented via proc or lambda.
I am trying to get an in depth understanding of ruby. As such I checking all kinds of unorthodox code. I am trying to understand why line 3 works while line 5 is an error.
x=123
def b(x)
p x
def a(u)
p x # why is this an error?!?!?
end
a 4
end
b 1
If a can't access b's parameters, why doesn't it access the global x=123?
Why does this work if I explicitly use change lines 1 & 5 to the global "$x"?
Why does this work if I use a lambda explictly?
This is purely a learning exercise, I am doing this to understand what is going on beneath the hood.

It's something called the "scope gate". Basically, when you start a definition of a method/class/module, a new scope is created and all local variables from other scopes can't be accessed. This doesn't apply to instance/global variables, you'll keep access to those.
Since a lambda isn't a method, it doesn't create new scope and reuses existing one instead.
Also,
why line 3 works
x = 123
def b(x)
p x # this "x" is "x the parameter", not "x the local variable from outer scope"
# that's why it works. If you tried to access the local var, it wouldn't work.
def a(u)
p x # like here, see? Doesn't work.
end
a 4
end
b 1

The first thing to understand is that def does not make a "function" (what does that even mean in Ruby?) -- def defines a method on some object or class. Even when it appears to be not inside any object, it still defines a method on the "main" object. So when you do def a, it is not "local" to the method b; it is a method a just like if you defined it at the top level, except that it doesn't get defined until b runs. It can be called as the method a from other places. You have nested method definitions.
Because it was meant to define methods, which most of the time is defined at the top level of a class or module, def was never made to capture outside variables.

Related

how does the assignment symbol work - Ruby

In Ruby if i just assign a local variable.
sound = "bang".
is that a main.sound=("bang") method? if so, where and how is that method "sound=" being defined? or how is that assignment working? if not, what is actually happening?
i know that for a setter method you would say x.sound=("bang"). and you are calling the method "sound=" on the object "x" with the argument "bang". and you are creating an instance variable "sound".
and i can picture all of that. but not when you assign a variable in the "main" object. as far as i know it isn't an instance variable of the Object class... or is it? I'm so confused.
In most programming languages, Ruby included, assignment is a strange beast. It is not a method or function, what it does is associate a name (also called an lvalue since it's left of the assignment) with a value.
Ruby adds the ability to define methods with names ending in = that can be invoked using the assignment syntax.
Attribute accessors are just methods that create other methods that fetch and assign member variables of the class.
So basically there are 3 ways you see assignment:
the primitive = operator
methods with names ending in =
methods generated for you by the attribute accessor (these are methods ending in =)
A variable assignment is just creating a reference to an object, like naming a dog "Spot". The "=" is not calling any method whatsoever.
As #ZachSmith comments, a simple expression such as sound could refer to a local variable named "sound"or a method of selfnamed "sound". To resolve this ambiguity, Ruby treats an identifier as a local variable if it has "seen" a previous assignment to the variable.
is that a main.sound=("bang") method?
No. main.sound="bang" should set instance variable or element of that variable.
With dot(main.sound) you tell object to do some method(in this case sound).
To manage local variables ruby create new scope.
class E
a = 42
def give_a
puts a
end
def self.give_a
puts a
end
binding
end
bin_e = _ # on pry
E.give_a # error
E.new.give_a # error
Both methods doesn't know about a. After you create your class, a will soon disappear, deleted by garbage collector. However you can get that value using binding method. It save local scope to some place and you can assign it to the variable.
bin.eval "a" # 42
lambdas have scope where they were defined:
local_var_a = 42
lamb = ->{puts local_var_a}
lamb.call() # 42

Generic way to replace an object in it's own method

With strings one can do this:
a = "hello"
a.upcase!
p a #=> "HELLO"
But how would I write my own method like that?
Something like (although that doesn't work obviously):
class MyClass
def positify!
self = [0, self].max
end
end
I know there are some tricks one can use on String but what if I'm trying to do something like this for Object?
Many classes are immutable (e.g. Numeric, Symbol, ...), so have no method allowing you to change their value.
On the other hand, any Object can have instance variables and these can be modified.
There is an easy way to delegate the behavior to a known object (say 42) and be able to change, later on, to another object, using SimpleDelegator. In the example below, quacks_like_an_int behaves like an Integer:
require 'delegate'
quacks_like_an_int = SimpleDelegator.new(42)
quacks_like_an_int.round(-1) # => 40
quacks_like_an_int.__setobj__(666)
quacks_like_an_int.round(-1) # => 670
You can use it to design a class too, for example:
require 'delegate'
class MutableInteger < SimpleDelegator
def plus_plus!
__setobj__(self + 1)
self
end
def positify!
__setobj__(0) if self < 0
self
end
end
i = MutableInteger.new(-42)
i.plus_plus! # => -41
i.positify! # => 0
Well, the upcase! method doesn't change the object identity, it only changes its internal structure (s.object_id == s.upcase!.object_id).
On the other hand, numbers are immutable objects and therefore, you can't change their value without changing their identity. AFAIK, there's no way for an object to self-change its identity, but, of course, you may implement positify! method that changes properties of its object - and this would be an analogue of what upcase! does for strings.
Assignment, or binding of local variables (using the = operator) is built-in to the core language and there is no way to override or customize it. You could run a preprocessor over your Ruby code which would convert your own, custom syntax to valid Ruby, though. You could also pass a Binding in to a custom method, which could redefine variables dynamically. This wouldn't achieve the effect you are looking for, though.
Understand that self = could never work, because when you say a = "string"; a = "another string" you are not modifying any objects; you are rebinding a local variable to a different object. Inside your custom method, you are in a different scope, and any local variables which you bind will only exist in that scope; it won't have any effect on the scope which you called the method from.
You cannot change self to point to anything other than its current object. You can make changes to instance variables, such as in the case string which is changing the underlying characters to upper case.
As pointed out in this answer:
Ruby and modifying self for a Float instance
There is a trick mentioned here that is a work around, which is to write you class as a wrapper around the other object. Then your wrapper class can replace the wrapped object at will. I'm hesitant on saying this is a good idea though.

Why is 'super' a keyword rather than a method in Ruby?

In Ruby, super is a keyword rather than a method.
Why was it designed this way?
Ruby's design tends toward implementing as much as possible as methods; keywords are usually reserved for language features that have their own grammar rules. super, however, looks and acts like a method call.
(I know it would be cumbersome to implement super in pure Ruby, since it would have to parse the method name out of caller, or use a trace_func. This alone wouldn't prevent it from being a method, because plenty of Kernel's methods are not implemented in pure Ruby.)
It behaves a little differently, in that if you don't pass arguments, all of the current arguments (and block, if present) are passed along... I'm not sure how that would work as a method.
To give a rather contrived example:
class A
def example(a, b, c)
yield whatever(a, b) + c
end
end
class B < A
def example(a, b, c)
super * 2
end
end
I did not need to handle the yield, or pass the arguments to super. In the cases where you specifically want to pass different arguments, then it behaves more like a method call. If you want to pass no arguments at all, you must pass empty parentheses (super()).
It simply doesn't have quite the same behaviour as a method call.
super doesn't automatically call the parent class's method. If you imagine the inheritance hierarchy of a ruby class as a list, with the class at the bottom and Object at the top, when ruby sees the the super keyword, rather than just check the the parent class, it moves up the entire list until it finds the first item that has a method defined with that name.
I'm careful to say item because it could also be a module. When you include a module in to a class, it is wrapped in an anonymous superclass and put above your class in the list I talked about before, so that means if you had a method defined for your class that was also defined in the module, then calling super from the class's implementation would call the module's implementation, and not the parent class's:
class Foo
def f
puts "Foo"
end
end
module Bar
def f
puts "Bar"
super
end
end
class Foobar < Foo
include Bar
def f
puts "Foobar"
super
end
end
foobar = Foobar.new
foobar.f
# =>
# Foobar
# Bar
# Foo
And I don't believe that it is possible to access this 'inheritance list' from the ruby environment itself, which would mean this functionality would not be available (However useful it is; I'm not every sure if this was an intended feature.)
Hm, good qustion. I'm not sure how else (besides using super) you would you reference the super version of a given method.
You can't simply call the method by name, because the way that polymorphism works (how it figures out which version of that method to actually call, based on the object class) would cause your method to call itself, spinning into an infinite set of calls, and resulting in a stack overflow.

referring to module level variables from within module

I'm having some difficulty with referring to module-level variables in ruby. Say I have a situation like this, where I'm referring to M.a internally:
module M
##a=1
def self.a
##a
end
class A
def x
M.a
end
end
end
Now, this example works fine for me but it is failing in a slightly more complicated context (where the module is spread over a number of files installed in a local gem - but my understanding is that that should not effect the way the code is executed) with an error like this: undefined method `a' for M::M (NoMethodError).
So, is this the correct way to refer to module level variables in context? is there a simpler/more idiomatic way?
If the module is spread out over other files, you need to ensure that your initialization is run before the method is called. If they are in the same file, this should be as much as guaranteed, but if you somehow split them there could be trouble.
I've found you can usually get away with this:
module M
def self.a
#a ||= 1
end
end
If this variable is subject to change, you will need a mutator method. Rails provides mattr_accessor that basically does what you want, part of ActiveSupport.

Why can you not declare constants in methods with Ruby?

Consider the following, StubFoo is a stub of Foo, which I wish to stub out for some tests.
class Runner
def run
Foo = StubFoo
foo = Foo.new
# using Foo...
end
end
This generates the following error message: Dynamic constant assignment
Yet, in RSpec I can do the following, which works and is perfectly legal:
it "should be an example" do
Foo = StubFoo
foo = Foo.new
foo.to_s.should == "I am stubbed!"
end
A few questions regarding this.
Why does this work with the RSpec test case, but not the method above?
As far as I'm aware, "it" is just a method within RSpec, yet I'm able to redeclare a constant within the "method".
I'm doing this prior to using a mocking framework for purely wanting to know how mocking, stubbing etc... is different in Ruby. I hear that dynamic languages are easier to mock/stub, and there are guides on the Internet in which simple class reassignment is done as above. From my research, in Ruby it's not possible to declare constants within a method, yet I'm confused as mentioned above.
Edit
Right, this is starting to make more sense. I've updated run to be using const_set now.
def run
old = Foo
self.class.const_set(:Foo, StubFoo)
foo = Foo.new
puts foo.to_s
self.class.const_set(:Foo, old)
foo = Foo.new
puts foo.to_s
end
This generates a warning however, is this what/how mocking frameworks work then in Ruby? Obviously much more elegant and feature full, but do they simply repress this warning?
You can't reassign constants in method definitions using Constant = value. You can however reassign them using const_set. Basically it's meant to discourage, but not disallow, dynamic constant reassignment.
As to why it works with it: Yes, it is a method, but you're not defining, you're calling it. The reassignment takes place inside the block which you pass as an argument to it.
Blocks inherit the type of the context in which they are created. This means that inside the block, self.class.name would be the name of your test class. Therefore, when you define a constant inside the block passed to the it method, you're actually defining a constant on your test class.
Because in your test, you are not defining a method, but rather calling it.
Ruby detects you are defining a constant in a method, that can be run multiple times, hence the warning. If you call the method more than once, you'll get a warning: already initialized constant Foo warning. This is because they're supposed to be constants. (What would happen, for example, if you defined Foo = Time.now?)
You are getting the error because if Runner#run is called, Foo will be changed unexpectedly on the caller. For example:
class C; end
def add_two_to(x) # this method is defined externally, and you cannot change it.
C = Class.new {} # error
x + 2
end
x = C.new
puts add_two_to(5)
y = C.new
x.is_a? y.class # would be false if the code get here
The caller of add_two_to does not expect C to be redefined. It just makes more sense that the last line of the above code will always be true. In your second example, your "method" is not actually a method, but a block. If you run the block twice (which it will not do), you will get this error:
warning: already initialized constant Q

Resources