Ruby noob here
I understand ruby does pass by reference for function parameters
However, I am getting the feeling this is slightly different from conventional c/c++ style pass by reference
Sample code:
def test1(str)
str += ' World!'
end
def test2(str)
str << ' World!'
end
str = 'Hello'
test1(str)
p str # Hello
test2(str)
p str # Hello World!
I would expect test1 to also return Hello World! if I were using references in c/c++.
This is simply out of curiosity -- any explanations would be appreciated
I understand ruby does pass by reference for function parameters
Ruby is strictly pass-by-value, always. There is no pass-by-reference in Ruby, ever.
This is simply out of curiosity -- any explanations would be appreciated
The simple explanation for why your code snippet doesn't show the result you would expect for pass-by-reference is that Ruby isn't pass-by-reference. It is pass-by-value, and your code snippet proves that.
Here is a small snippet that demonstrates that Ruby is, in fact, pass-by-value and not pass-by-reference:
#!/usr/bin/env ruby
def is_ruby_pass_by_value?(foo)
foo << <<~HERE
More precisely, it is call-by-object-sharing!
Call-by-object-sharing is a special case of pass-by-value,
where the value is always an immutable pointer to a (potentially mutable) value.
HERE
foo = 'No, Ruby is pass-by-reference.'
return
end
bar = ['Yes, of course, Ruby *is* pass-by-value!']
is_ruby_pass_by_value?(bar)
puts bar
# Yes, of course, Ruby *is* pass-by-value!,
# More precisely, it is call-by-object-sharing!
# Call-by-object-sharing is a special case of pass-by-value,
# where the value is always an immutable pointer to a (potentially mutable) value.
Ruby does however allow mutation of objects, it is not a purely functional language like Haskell or Clean.
In the first case a new object was created when you did str += ' World!'
str = "Hello"
=> "Hello"
str.object_id
=> 69867706917360
str += " World"
=> "Hello World"
str.object_id
=> 69867706885680
str = "Hello"
=> "Hello"
str.object_id
=> 69867706856200
str << " World"
=> "Hello World"
str.object_id
=> 69867706856200
str = "Hello"
=> "Hello"
str.object_id
=> 69867706786780
str.freeze
=> "Hello"
str << " World"
RuntimeError: can't modify frozen String
str += " World"
=> "Hello World"
"<<" is a Binary Left Shift Operator. The left operands value is moved left by the number of bits specified by the right operand.
So "<<" doesn't create a new string, str.contact("World") doesn't create a new string as well.
The method test1 doesn't have to do anything with the returned result , you can try this method :
def test1(str)
str.concat(' World!')
end
Look at the following adaption of your test, by showing the object_id of your object you can easily see if it is the same or not. Test1 returns another String object because of the += concatenation but it is not used afterward.
This looks like passed by reference but in reality it is the value of the pointer to the object that is passed. The best explenation I could find for this is here , the author calls it pass-reference-by-value
def test1(str)
p ["in test1 before", str.object_id]
str += ' World!'
p ["in test1 after", str.object_id]
str
end
def test2(str)
p ["in test2", str.object_id]
str << ' World!'
end
str = 'Hello'
p ["in main", str.object_id]
test1(str)
p str # Hello
p ["after test1", str.object_id]
test2(str)
p str
p ["after test2", str.object_id]
Gives
["in main", 12363600]
["in test1 before", 12363600] # the same object, so pointer to object passed by value
["in test1 after", 12362976] # returns a new object, the old is unchanged
"Hello"
["after test1", 12363600] # idem
["in test2", 12363600]
"Hello World!"
["after test2", 12363600]
# still the same object
STRING IS REFERENCED, in ruby except value like numbers, true, false, nil, others are referenced.
a = "hello"
b = a
a.replace("Hola")
p a # Hola
p b # Hola
You would wanna add the magic comment at the beginning.:
# frozen_string_literal: true
a = "hello"
b = a
a.replace("Hola") # can't modify frozen String: "hello" (FrozenError)
def test1(str)
str += ' World!'
end
operator += is a syntactic sugar in ruby. Expression a += b translates to a = a + b. Operator + applied on String instance creates new String, which is a concatenation of the two arguments. That is why str is not modified in the first case.
Also I'd like to correct your statement:
I understand ruby does pass by reference for function parameters
Actually ruby passes by reference every parameter except the "value types" - ie. values nil, true, false and instances of class Fixnum
Related
Lets say i want to remove leading space from a string
a = " Hello world"
puts a.gsub!(/^ /,"") # => "Hello World"
But if there is no leading space in the string
b = "Hello World"
puts a.gsub!(/^ /,"") # => nil
Now if I use just gsub instead of gsub it returns the string:
puts b.gsub(/^ /,"") # => "Hello World"
puts a.gsub(/^ /,"") # => "Hello World" (works for both a and b)
So is it possible to get gsub! to perform like gsub as shown above?
the reason is because gsub would create a new object everytime which I would like to avoid because I will be using at least 4 or 5 gsubs on the string I need to manipulate.
thanks
Here are two ways of doing that.
Add || a to the gsub expression
a.gsub!(/\A\s/,"") || a
would give you the desired result, namely removing the space, if present, and returning the string after any change is made. If no change is made, this reduces to nil || a #=> a. If a change is made the expression reduces to a.gsub!(/\A\s/,""). For example,
a = " Hello"
a.gsub!(/\A\s/,"") || a #=> "Hello"
a #=> "Hello"
a ="Hello"
a.gsub!(/\A\s/,"") || a #=> "Hello"
a #=> "Hello"
Although it makes no difference here, I prefer to use the beginning of string anchor, \A, rather than the beginning of line anchor, ^, I also prefer using a whitespace character \s to a space (assuming it couldn't be a tab) to a space, as an inadvertent extra space might not be noticed.
Use String#replace and the non-destructive method String#gsub
a = " Hello"
a.replace(a.gsub(/\A\s/,"")) #=> "Hello"
a #=> "Hello"
a ="Hello"
a.replace(a.gsub(/\A\s/,"")) #=> "Hello"
a #=> "Hello"
Answer:
b.gsub!(/(^ )?/,"") => "Hello World"
Explanation:
(...) Capture everything enclosed
a? Zero or one of a
— via http://rubular.com
Example:
irb(main):008:0> a = " Hello World"
=> " Hello World"
irb(main):009:0> b = "Hello World"
=> "Hello World"
irb(main):010:0> a.gsub!(/(^ )?/,"")
=> "Hello World"
irb(main):011:0> b.gsub!(/(^ )?/,"")
=> "Hello World"
Alternative:
b.gsub!(/^ |/,"") # => "Hello World" using "OR" pipe char
The code below adds a method to the String class that simply calls gsub! and then returns the String object that it was called on. So if the gsub! was "successful" and something has been subbed it returns the subbed string. Otherwise it will return the original string.
class String
def filled_gsub!(oldsubstr, newsubstr)
gsub!(oldsubstr, newsubstr)
self
end
end
I can't tell what's wrong with my code:
def morse_code(str)
string = []
string.push(str.split(' '))
puts string
puts string[2]
end
What I'm expecting is if I use "what is the dog" for str, I would get the following results:
=> ["what", "is", "the", "dog"]
=> "the"
But what I get instead is nil. If I do string[0], it just gives me the entire string again. Does the .split function not break them up into different elements? If anyone could help, that would be great. Thank you for taking the time to read this.
Your code should be :
def morse_code(str)
string = []
string.push(*str.split(' '))
puts string
p string[2]
end
morse_code("what is the dog" )
# >> what
# >> is
# >> the
# >> dog
# >> "the"
str.split(' ') is giving ["what", "is", "the", "dog"], and you are pushing this array object to the array string. Thus string became [["what", "is", "the", "dog"]]. Thus string is an array of size 1. Thus if you want to access any index like 1, 2 so on.., you will get nil. You can debug it using p(it calls #inspect on the array), BUT NOT puts.
def morse_code(str)
string = []
string.push(str.split(' '))
p string
end
morse_code("what is the dog" )
# >> [["what", "is", "the", "dog"]]
With Array, puts works completely different way than p. I am not good to read MRI code always, thus I take a look at sometime Rubinious code. Look how they defined IO::puts, which is same as MRI. Now look the specs for the code
it "flattens a nested array before writing it" do
#io.should_receive(:write).with("1")
#io.should_receive(:write).with("2")
#io.should_receive(:write).with("3")
#io.should_receive(:write).with("\n").exactly(3).times
#io.puts([1, 2, [3]]).should == nil
end
it "writes nothing for an empty array" do
x = []
#io.should_receive(:write).exactly(0).times
#io.puts(x).should == nil
end
it "writes [...] for a recursive array arg" do
x = []
x << 2 << x
#io.should_receive(:write).with("2")
#io.should_receive(:write).with("[...]")
#io.should_receive(:write).with("\n").exactly(2).times
#io.puts(x).should == nil
end
We can now be sure that, IO::puts or Kernel::puts behaves with array just the way, as Rubinious people implemented it. You can now take a look at the MRI code also. I just found the MRI one, look the below test
def test_puts_recursive_array
a = ["foo"]
a << a
pipe(proc do |w|
w.puts a
w.close
end, proc do |r|
assert_equal("foo\n[...]\n", r.read)
end)
end
Can anyone explain why foo is mutated in version 1? What is the difference between << and = assignment?
VERSION 1
foo = "apple"
bar = foo
"foo: #{foo}" # => foo: apple
bar << "hello"
"bar: #{bar}" # => bar: applehello
"foo: #{foo}" # => foo: applehello
VERSION2
foo = "apple"
bar = foo
"foo: #{foo}" # => foo: apple
bar = bar + "hello"
"bar: #{bar}" # => bar: applehello
"foo: #{foo}" # => foo: apple
Because = is an assignment as you said.
But << is not an assignment - it's concatenation operator when the left operand is a string.
So:
bar = bar + "hello"
creates a new string by joining contents of bar with "hello" and then this new string is assigned to variable bar, while:
bar << "hello"
does the in-place concatenation of string - bar won't be set to new string but the string it holds will be modified.
So with << bar and foo still keep reference to the same object while with = only bar gets a new value.
You're setting bar as a reference to foo. The << operator works in place, as in the first version, and in the second version you're using + which produces a new value, while not changing the original.
bar << "hello" appends to bar (which is foo), while bar = bar + "hello" creates a copy of the string, foo remains untouched.
String concatenation with + returns a new object:
http://www.ruby-doc.org/core-2.1.0/String.html#method-i-2B
The append operator acts on the object that the reference points to:
http://www.ruby-doc.org/core-2.1.0/String.html#method-i-3C-3C
In the first example you are appending to the object that both foo and bar point to.
In the second example you are adding "hello" to the object that bar points to which returns a new object which, in turn, bar now points to all the while foo still points to the object whose value is still just "apple"
First observe the following:--
str = "test"
#=> "test"
str[1]
#=> "e"
str1 = str
#=> "test"
str.object_id
#=> 8509820
str1.object_id
#=> 8509820
So string is stored as an array of each character in Ruby. Other language like Java also returns complete string if you just use char type reference. Similarly here also we get each char of second string added to array of characters for first string.
str << "string"
#=> "teststring"
str1
#=> "teststring"
str.object_id
#=> 8509820
str1.object_id
#=> 8509820
Here no new object gets created. Same array holds each characters of second string.
Now observe the following:--
str = "test"
#=> "test"
str1 = str
str.object_id
#=> 9812345
str1.object_id
#=> 9812345
str = str + "string"
#=> "teststring"
str.object_id
#=> 9901234
str1
#=> "test"
str1.object_id
#=> 9812345
Here we see + operator causes creation of a new object.
I have the following string and want to define a method to append the letter 'd' to the end of it. One way I can do this is with the class << str idiom.
str = 'abc'
class << str
def frob
return self + "d"
end
end
Is there a way I can do this with the following syntax?
def str.frob
return str + 'd'
end
When I try to do this I get the undefined local variable or method 'str' for "abc":String (NameError)
str = 'abc'
def str.frob
p self
return self + 'd'
end
p str.frob
# >> "abc"
# >> "abcd"
str.singleton_methods # => [:frob]
str.object_id # => 79771730
You got the error because singleton_method #frob has its own local scope,where str is not legal. Thus you got the legitimate error. That method is created on the object "abc" having object_id,79771730.
I am new to Ruby and to this site.
The following two functions are different, one alters the variable outside the function and one does not.
def m1 (x)
x << "4"
end
def m2 (x)
x = x + "4"
end
str="123"
m2(str) #str remains unchanged 123
m1(str) #str is changed to 1234
I would like to make sure I understand this correctly -
When m1 is called, the reference to str is copied and passed to the function which sees it as x. Operator << changes x which references the origial str so str is changed by this operation.
When m2 is called, the reference to str is copied and passed to the function which sees it as x. Operator + creates a new string, and the assignment x = x + "4" simply redirects x to the new string leaving the original str variable untouched.
Right?
Thanks
String#+ :: str + other_str → new_str Concatenation—Returns a new String containing other_str concatenated to str.
String#<< :: str << integer → str : Append—Concatenates the given object to str.
<< doesn't create the new object, where as + does.
a = "str"
#=> "str"
a.object_id
#=> 14469636
b = a << "ing"
#=> "string"
a.object_id
#=> 14469636
b.object_id
#=> 14469636
a= "str"
#=> "str"
b = a + "ing"
#=> "string"
a.object_id
#=> 16666584
b.object_id
#=> 17528916
EDIT
From your comment, got your point. See below:
def m1 (x)
x << "4"
end
#=> nil
def m2 (x)
x = x + "4"
end
#=> nil
str="123"
#=> "123"
m2(str)
#=> "1234"
str
#=> "123"
Here str didn't change as you passed the value inside the method m2(), all the changes local to the method as per the above call. Thus below str not showing that change.To see the change you have to call it as below.
str = m2(str)
#=> "1234"
str
#=> "1234"
OR
You could do the stuff as below :- where I passed reference address of str but not the value.
str = "abc"
#=> "abc"
str.object_id
#=> 16250484
ObjectSpace._id2ref(16250484)
#=> "abc"
def m1 (x)
ObjectSpace._id2ref(x) << "4"
end
#=> nil
m1(16250484)
#=> "abc4"
str
#=> "abc4"
Hope it make sense :)
Cheers!
<< the concatenate operator is destructive to a string. This means that it will manipulate the variable it acts upon, not just return the result of the expression.
example:
str = "abc"
puts str + "d" # "abcd"
puts str # "abc"
puts str << "d" # "abcd"
puts str # "abcd"
The following two functions are different, one alters the variable outside the function and one does not.
This is wrong. Neither of the two methods (they are methods, BTW, not functions, Ruby doesn't have functions; there is a fundamental difference) alters the str variable. m1 modifies the object the variable points to, but that is completely different from modifying the variable itself.