What's the difference between TOPLEVEL_BINDING and binding? - ruby

I opened irb and checked the value of binding and TOPLEVEL_binding:
2.7.0 :021 > TOPLEVEL_BINDING
=> #<Binding:0x00007fbd7d85bb18>
2.7.0 :022 > binding
=> #<Binding:0x00007fbd7c2ce908>
My understanding was that TOPLEVEL_binding and binding in a new irb session should both point to the same object: the binding object for main's execution context. Why are they different?

TOPLEVEL_BINDING is a constant.
binding is a method which creates a new binding specific to the place where it is called.
> TOPLEVEL_BINDING #=> #<Binding:0x0000564e7226f1d0>
> TOPLEVEL_BINDING #=> #<Binding:0x0000564e7226f1d0> -- same object
> binding #=> #<Binding:0x0000564e72892aa8>
> binding #=> #<Binding:0x0000564e728907f8> -- new object
> TOPLEVEL_BINDING.source_location #=> ["<main>", 0]
> binding.source_location #=> ["(irb)", 6]
> binding == binding #=> false
> x = 2
> b = binding
> z = 4
> TOPLEVEL_BINDING.eval('x') #=> NameError
> TOPLEVEL_BINDING.eval('z') #=> NameError
> b.eval('x') #=> 2
> b.eval('z') #=> NameError
> binding.eval('x') #=> 2
> binding.eval('z') #=> 4

Related

rails nested hash with default values [duplicate]

This question already has answers here:
Strange, unexpected behavior (disappearing/changing values) when using Hash default value, e.g. Hash.new([])
(4 answers)
Closed 2 years ago.
I'd like to create a new Hash with nested default values. I thought it should be like
h = Hash.new(count: 0, rating: 0)
So I can do stuff like
h['a'][:count] += 1
h['a'][:rating] += 1
and so on. But when I try it in the console it looks like this:
2.3.1 :046 > h = Hash.new(count: 0, rating: 0)
=> {}
2.3.1 :047 > h["a"]
=> {:count=>0, :rating=>0}
2.3.1 :048 > h["a"][:count]
=> 0
2.3.1 :049 > h["a"][:count] += 1
=> 1
2.3.1 :050 > h["b"][:count] += 1
=> 2
2.3.1 :051 > h
=> {}
So my questions are:
Why is h["b"][:count] += 1 returning 2 and not 1?
why is h empty?
Thanks in advance!
The doc for Hash::new explains the three ways of initializing a hash and, in your case, you are using an object in the Hash constructor:
If obj is specified, this single object will be used for all default values.
If you want that each missing key creates it's own object, create the hash with a block, like this:
h = Hash.new { |h,k| h[k] = { count: 0, rating: 0 } }
Then:
2.6.3 :012 > h
=> {}
2.6.3 :013 > h['a'][:count] = 5
=> 5
2.6.3 :015 > h
=> {"a"=>{:count=>5, :rating=>0}}
You can find this behaviour documented in the Hash::new documentation:
new → new_hash
new(obj) → new_hash
new {|hash, key| block } → new_hash
Returns a new, empty hash. If this hash is subsequently accessed by a
key that doesn't correspond to a hash entry, the value returned
depends on the style of new used to create the hash. In the first
form, the access returns nil. If obj is specified, this single
object will be used for all default values. If a block is
specified, it will be called with the hash object and the key, and
should return the default value. It is the block's responsibility to
store the value in the hash if required.
h = Hash.new("Go Fish")
h["a"] = 100
h["b"] = 200
h["a"] #=> 100
h["c"] #=> "Go Fish"
# The following alters the single default object
h["c"].upcase! #=> "GO FISH"
h["d"] #=> "GO FISH"
h.keys #=> ["a", "b"]
# While this creates a new default object each time
h = Hash.new { |hash, key| hash[key] = "Go Fish: #{key}" }
h["c"] #=> "Go Fish: c"
h["c"].upcase! #=> "GO FISH: C"
h["d"] #=> "Go Fish: d"
h.keys #=> ["c", "d"]
In your example h["a"] and h["b"] both return the exact same default hash object. Meaning that if you change h["a"], h["b"] is also changed. Since you never set h["a"] or h["b"] the hash appears empty.
To assign a new hash on access you'll have to use the block syntax as shown by luis.parravicini.

NoMethodError: undefined method `except' for Hash

While using except on Hash in Ruby,
d = {}
d["a"]=1234
d["b"]=34
d["c"]=3
d.except(:b,:c)
I am getting NoMethodError:
NoMethodError: undefined method `except' for {"a"=>1234, "b"=>34, "c"=>3}:Hash from (irb):6 from
/Users/niranjan/.rvm/rubies/ruby-1.9.3-p551/bin/irb:12:in `<main>'
What am I doing wrong?
except is a Rails method (ActiveSupport to be exact). Your code does not reproduce that error when executing in Rails console:
> d = {}
# => {}
> d["a"]=1234
# => 1234
> d["b"]=34
# => 34
> d["c"]=3
# => 3
> d.except(:b,:c)
# => {"a"=>1234, "b"=>34, "c"=>3}
There is no Hash#except. You can implement it as follows:
d.reject { |k, v| ["b", "c"].include? k }
# => {"a"=>1234}
Note that it is not a Hash with indifferent access; "b" is not the same thing as :b.
As they have said above, there is no method 'except' meaning you have not defined 'except' anywhere in this code. If you need a refresher on how to build Hashes, a good one is: http://www.tutorialspoint.com/ruby/ruby_hashes.htm
Hash#except will be in Ruby 3
h = { a: 1, b: 2, c: 3 }
p h.except(:a) #=> {:b=>2, :c=>3}
https://www.ruby-lang.org/en/news/2020/09/25/ruby-3-0-0-preview1-released/

Ruby 2D indexer

How to implement proper 2D indexer with the following class? The following was my first shot
class MyArray
#init 2D array, set all elements at 0
def initialize(size)
#array = []
0.upto(size - 1) {|x|
#array[x] = []
0.upto(size - 1) {|y|
#array[x][y] = 0
}
}
end
def [](*args)
#array[args[0]][args[1]]
end
def []=(*args)
#array[args[0]][args[1]] = args[2]
end
end
and it works just fine for
test = MyArray.new(3)
test[1, 1] = 5
but I would like to make it working also for
test[1][1] = 5
which now gives a compilation error
in `[]': no implicit conversion from nil to integer (TypeError)
in the [] method.
I doubt there is any need for a class for that task in Ruby. create and index a 2d array is ruby is very simple :
1.9.3p194 :001 > a = Array.new(3){[]} #create a array with 3 rows
=> [[], [], []]
1.9.3p194 :002 > a[1][2]=3 #assignment
=> 3
1.9.3p194 :003 > a[1][2] #index
=> 3
1.9.3p194 :004 > a
=> [[], [nil, nil, 3], []]

Non destructive methods in Ruby

I have a class which inherits from Array (in practice, it's just a mask for a multidimensional array).
I want to override its to_a method:
def to_a
self.each.with_index { |el, i| el.map {|j| j} }
end
but this messes things up: when I try to test my function:
it 'should be non destructive' do
a_board = Representation.new(#a_size)
a_clean_board = Representation.new(#a_size)
expect(a_board).to eq(a_clean_board)
# Try to modify a_board
arr = a_board.to_a
arr.pop
a_board.to_a.pop
# Check that it stayed equal to a_clean_board
expect(a_board).to eq(a_clean_board)
end
both calls to pop have side effects on the original board.
How can I avoid this?
Probably because it returns reference to the same object. To avoid this use map instead of each or use .dup at the end.
UPD
As I said, just use map. Like it's in functional programming where intentionally are no side-effects. Example:
class WrappedArr < Array
def to_a
map { |el| el.map {|el2| el2 } }
end
end
w_arr = WrappedArr.new([[1,2], [2,3]])
# => [[1, 2], [2, 3]]
2.0.0p247 :012 > w_arr.to_a.object_id # always different as it is different object
#=> 70318090081080
2.0.0p247 :013 > w_arr.to_a.object_id
# => 70318095088040
2.0.0p247 :014 > w_arr.to_a.object_id
# => 70318095081540
# final test
2.0.0p247 :015 > w_arr.to_a.pop
# => [2, 3]
2.0.0p247 :016 > w_arr
# => [[1, 2], [2, 3]]

Changing state of Ruby objects changes other class members?

I have a class that primarily implements some logic around a multi-dimensional array, which is essentially a grid of numbers. These numbers can swap positions, etc.. However, when they swap, other objects of the same class also appear to be modified. I'm not sure why.
I'm using instance variables to store the grid, so I don't understand why changes are apparently affecting other class members.
Here's a simplified example;
class TestGrid
attr_accessor :grid
#grid = []
def initialize(newgrid)
#grid = newgrid
end
def to_s
out = ""
#grid.each{|row|
out += row.join("|") + "\n"
}
out
end
def swap(x, y)
#grid[x[0]][x[1]], #grid[y[0]][y[1]] = #grid[y[0]][y[1]], #grid[x[0]][x[1]]
end
end
When we interact with a single instance in IRB, things look fine;
1.9.3-p385 :001 > require './TestGrid.rb'
=> true
1.9.3-p385 :002 > x = TestGrid.new([[1,2],[3,4]])
=> 1|2
3|4
1.9.3-p385 :003 > x.swap([0,1],[1,1])
=> [4, 2]
1.9.3-p385 :004 > puts x
1|4
3|2
=> nil
However, if I create a second instance by cloning or duping;
1.9.3-p385 :006 > x = TestGrid.new([[1,2],[3,4]])
=> 1|2
3|4
1.9.3-p385 :007 > y = x.clone
=> 1|2
3|4
1.9.3-p385 :008 > x.swap([0,1],[1,1])
=> [4, 2]
1.9.3-p385 :009 > puts x
1|4
3|2
=> nil
1.9.3-p385 :010 > puts y
1|4
3|2
=> nil
Why are my changes to x also being applied to y? From my understanding of Object#Clone, theses are supposed to be distinct instance, unrelated to each other. Their object ID's would seem to support that expectation;
1.9.3-p385 :012 > puts "#{x.object_id} #{y.object_id}"
70124426240320 70124426232820
For reference, I ended up creating an initialize_copy method which ensures the affected parameter is deep copied. I didn't really like the idea of Marshalling objects around just to copy an array deeply, so I decided on this instead.
def initialize_copy(original)
super
#grid = []
original.grid.each{|inner|
#grid << inner.dup
}
end
By default, dup and clone produce shallow copies of the objects they are invoked on. Meaning that x and y in your example still reference the same area of memory.
http://ruby-doc.org/core-2.0/Object.html#method-i-dup
http://ruby-doc.org/core-2.0/Object.html#method-i-clone
You can override them inside of your customized class to produce a deep copy in a different area of memory.
A common idiom in Ruby is to use the Marshal#load and Marshal#dump methods of the Object superclass to produce deep copies. (Note: these methods are normally used to serialize/deserialze objects).
def dup
new_grid = Marshal.load( Marshal.dump(#grid) )
new_grid
end
irb(main):007:0> x = TestGrid.new([[1,2],[3,4]])
=> 1|2
3|4
irb(main):008:0> y = x.dup
=> [[1, 2], [3, 4]]
irb(main):009:0> x.swap([0,1],[1,1])
=> [4, 2]
irb(main):010:0> puts x
1|4
3|2
=> nil
irb(main):011:0> y
=> [[1, 2], [3, 4]]
irb(main):012:0> puts y
1
2
3
4
=> nil
irb(main):013:0>
y remains the same after the swap.
Alternatively, create a new array, iterate through #grid and push its subarrays into the array.
def dup
new_grid = []
#grid.each do |g|
new_grid << g
end
new_grid
end

Resources