0

I have this simple test:

def swap(a, b)
   c = a
   a = b
   b = c
end

a = 3
b = 4
swap(a, b)
print a # 3. not change value

I know this example will not work on some languages such as Java or C# because those variables are primitives. But in Ruby, if everything is an object, I think the above example should work. Please explain this to me.


I think the problem is because FixNum is immutable. I have proof-of-concept code using Java to make an integer class that is not immutable:

static class CustomInteger {
        Integer value;
        public CustomInteger(int i) {
            this.value = i;
        }

        public void changeValue(int i) {
            this.value = i;
        }

        public int getValue() {
            return value;
        }

        public CustomInteger setValueImmutable(int i) {
           return new CustomInteger(i);
        }
    }

    public static void swap(CustomInteger a, CustomInteger b) {
        int c = a.getValue();
        a.changeValue(b.getValue());
        b.changeValue(c);
    }

    public static void main(String[] args) {
        CustomInteger i1 = new CustomInteger(3);
        CustomInteger i2 = new CustomInteger(4);
        swap(i1, i2);
        System.out.println(i1.getValue());
    }

public static void notWorkSwap(Integer a, Integer b) {
        int temp_a = a;
        int temp_b = b;
        a = temp_b;
        b = temp_a;
    }
the Tin Man
  • 158,662
  • 42
  • 215
  • 303
Trần Kim Dự
  • 5,872
  • 12
  • 55
  • 107
  • `Fixnum` objects are immutable. See [this answer](http://stackoverflow.com/a/161589/830729). – Kapitol Feb 01 '17 at 17:21
  • It doesn't work (like it doesn't in java/c# etc) not because something is an object or not, but because of how assignment works. I don't know of any language where this will actually swap them. – ndnenkov Feb 01 '17 at 17:23
  • @Kapitol ah I understood. because Fixnum is immutable :D that is the main point :D – Trần Kim Dự Feb 01 '17 at 17:26
  • @ndn yep. in C/C++, I can use pointer/reference. In C# doable also. but I forgot. But in Java, you cannot except using Integer, Double ... – Trần Kim Dự Feb 01 '17 at 17:27
  • 1
    @TrầnKimDự, I'm a 1000% percent certain that this exact code will not swap them in c/c++/c#/java/whatever. If you pass pointers and change the address of these pointers - yes. But not with regular assignment like that. – ndnenkov Feb 01 '17 at 17:29
  • @ndn My fault. Java behavior still be same. because Integer, Double also immutable, too. For example, we don't use Integer here, but use a custom object. for change value, we will use something such as: `person_1.setAge(newAge)`. so `person_1` outside at main method will change age, right. that because person object is mutable. – Trần Kim Dự Feb 01 '17 at 17:36
  • @TrầnKimDự, again - it has nothing to do with immutability. If you pass mutable objects (like arrays for example) and swap them, it will not take effect. But if you try to add an element for example, this will work. The reason is that you change the actual value, not just what the variable is pointing to. – ndnenkov Feb 01 '17 at 17:38
  • @ndn I have updated my question about Java code for proof-of-concept why the problem here is immutable. – Trần Kim Dự Feb 01 '17 at 17:44
  • 1
    @TrầnKimDự, it's not the reason why. Notice how you do `a.changeValue(b.getValue())` instead of `a = b` or `a = b.getValue()`. Assignment (`=`) just changes what the local variable refers to. It doesn't matter if the previous or new value is mutable or not. – ndnenkov Feb 01 '17 at 17:46
  • @ndn you should notice. that is not normal assignment. In java it called auto boxing / unboxing. a = 3 same as a.setValue(3), but here, it doesn't change old object but return new object. – Trần Kim Dự Feb 01 '17 at 17:47
  • I have updated my answer by adding method `setValueImmutable`. that how java works when you assign for example a = 3. – Trần Kim Dự Feb 01 '17 at 17:50
  • 1
    @TrầnKimDự, boxing/unboxing is the magical conversion between primitives and objects of corresponding classes. This has nothing to do with it. Calling `changeValue` is not "assignment for objects", it's calling a method that assigns value to the instance variable. It is still calling a method, not assignment. – ndnenkov Feb 01 '17 at 17:51
  • @TrầnKimDự, seriously, assignment is just the equals sign (`=`). Try to use `x = y` instead of `x.changeValue(y.getValue())` and you will get the same result (no swap), even though the values behind `x` and `y` are mutable. – ndnenkov Feb 01 '17 at 17:54
  • I understand. I mean when you use `Integer a = 3`. this is not normal assignment. In fact, It will called `Integer a = new Integer(3)` (simplified model, because Java has cache mechanism here). so when I coded "c=a; a=b; b=c" (a, b, c is Integer), in fact Java is set new value for object (not just change pointer). That why I can use my above example for proof-of-concept why swap two Integer in Java not work. – Trần Kim Dự Feb 01 '17 at 17:54
  • @ndn Please take a look at my Edit 2. I have made it clearer. i get value from Integer a and b (primitive). after that, I assign again for object a and b (auto boxing). but the reason it doesn't work because immutable. – Trần Kim Dự Feb 01 '17 at 17:59
  • I understand your point about x = y just simply assignment. But in this case, it doesn't work because immutable. because if it doesn't immutable, I *have changed* value from object a and b in Edit 2 and it should change outside of swap method. – Trần Kim Dự Feb 01 '17 at 18:01
  • @TrầnKimDự, I wrote an example with assignment and mutable objects [here](https://repl.it/F36T/0). As you can see, mutable or not, it doesn't change how assignment works. Boxing/unboxing doesn't change the fact that the assignment will only change the reference of the local variable, not mutate an existing object. – ndnenkov Feb 01 '17 at 18:09
  • @ndn I understood your example. Your example just mean swap method just swaps reference variables (and does not change value). But at my edit 2, I have get value outside and assign to primitive variable. ( **int** temp_b = b; **int** temp_a = a;) to force java boxing here (not just simply assign new reference but change value internal) and it still not work. that because immutable. – Trần Kim Dự Feb 01 '17 at 18:15
  • 1
    @TrầnKimDự, it does not work because your assumption that unboxing changes the value is incorrect. It does not change anything, it simply gives you the underlying primitive value. Boxing doesn't change anything either, it creates a new object that wraps that value. After whatever wrapping/unwrapping is done on the right hand side, it still simply assigns like a normal assignment. – ndnenkov Feb 01 '17 at 18:24
  • @ndn I'm thinking again. `a = 3` means `a = new Integer(3)`. so you said correctly, that not the result of immutable but just result of de-reference object. Before that, I think `a = 3` mean take object where **a is point to**. change that value and return new object. (not modify old object). nonsense but because that time I haven't thought carefully. My fault :D – Trần Kim Dự Feb 01 '17 at 18:32

2 Answers2

3

Your swap method only swaps variables that are local to the method.

Inside the method, a and b are swapped. This doesn't affect anything outside the method, though. c isn't defined outside swap for example.

Note that to swap a and b, you don't need any other variable : a, b = b, a is a valid Ruby syntax.

Finally, variables aren't objects. They merely reference objects.

Here's a way to achieve what you wanted to do. UPDATE: It's basically the equivalent of your Java code :

class MyObject
  attr_accessor :x
  def initialize(x)
    @x = x
  end
end

def swap(f,g)
  f.x, g.x = g.x, f.x
end

a = MyObject.new(1)
b = MyObject.new(2)

swap(a, b)

puts a.x
#=> 2
puts b.x
#=> 1

Note that the objects a and b haven't been swapped. Their x values have been swapped, though.

Another possibility would be to use instance variables :

def swap
  @a, @b = @b, @a
end

@a = 1
@b = 2

swap

p @a
#=> 2
p @b
#=> 1
Community
  • 1
  • 1
Eric Duminil
  • 52,989
  • 9
  • 71
  • 124
2

The reason this doesn't work is that method parameters are passed by value, not by reference. Only a small number of languages use pass-by-reference semantics by default. The behavior is the same in Java and in Ruby.

However, it's just tricky enough that plenty of people misuse the terminology. See this question, for example. Is Java "pass-by-reference" or "pass-by-value"?

In many languages, such as C#, pass-by-reference is possible with a special keyword (ref in the case of C#, or the & marker after a type in C++). You can simulate the behavior in languages like C by passing indirect pointers (though that gives you a bit more power, thanks to less strict type checking).

I haven't written C# code in a couple years, but what you are trying to achieve would be approximately:

static void SwapStrings(ref string s1, ref string s2)
        {
            string temp = s1;
            s1 = s2;
            s2 = temp;
        } 

It would be invoked SwapStrings(ref str1, ref str2);

This code is an abbreviated version of what's here: https://msdn.microsoft.com/en-us/library/s6938f28.aspx

Community
  • 1
  • 1
JasonTrue
  • 19,244
  • 4
  • 34
  • 61
  • They are local because they are values scoped to that function. If you had a byref a, byref b they'd now be references, still technically local variables, but made available to the calling function since the caller has the reference. – JasonTrue Feb 01 '17 at 17:28
  • 1
    You're right in that sense; my C is a bit rusty these days, and IIRC you had to use double-indirection to support a swap in C. – JasonTrue Feb 01 '17 at 17:42
  • I think the problem here is because FixNum in Ruby are immutable. If they're mutable, it works. Please take a look at my updated question. I have post code Java for proof-of-concept. – Trần Kim Dự Feb 01 '17 at 17:45
  • Yes, you can simulate a swap by creating an object that is mutable (Essentially a reference-capturing object). In this case you're swapping an instance variable rather than the reference to the parent object. – JasonTrue Feb 01 '17 at 18:02
  • 2
    @ndn: Yes, C still passes pointers by value. C passes everything by value, just like Ruby. (In fact, Ruby is basically like always passing pointers by value, and since everything being passed is always a pointer, there is no special syntax to take and dereference a pointer like there is in C.) But in languages like C++ and C♯, which *do* have pass-by-reference semantics, you *can* affect references in the callers's scope; in fact, that's pretty much the definition of pass-by-reference. – Jörg W Mittag Feb 02 '17 at 00:15
  • 1
    @TrầnKimDự: The Java code and the Ruby code you posted are completely different. In the Ruby code you are modifying references, in the Java code you are modifying objects. Those are completely different things. A Post-It with "Trần Kim Dự" on it is not the same as you. It is only a reference to you. – Jörg W Mittag Feb 02 '17 at 00:17
  • @JörgWMittag, agreed. – ndnenkov Feb 09 '17 at 14:32