4

Currently I'm developing a Watcher class in Ruby, which, among other things, is able to find the period duration of a toggling signal. This is in general rather simple, but a problem I'm facing is that apparently Ruby passes all parameters by value. While researching online I found many different discussions about what "pass by value" and "pass by reference" actually is, but no actual "how to". Coming from a C/C++ background, for me this is an essential part of a programming/scripting language.

The relevant parts of the class that I wrote are shown below. The method Watcher::watchToggling() is the one I'm having trouble with. As a parameter, aVariable should be the reference to the value I'm measuring the period duration for.

class Watcher
  @done
  @timePeriod

  def reset()
    @done = false;
    @timePeriod = nil;
  end

  def watchToggling(aVariable, aTimeout=nil)
    oldState = aVariable;
    oldTime = 0;
    newTime = 0;

    timeout(aTimeout)do
      while(!@done)
        newState = aVariable;
        if(newState != oldState)
          if(newState == 1)
            # rising edge
            if(oldTime != 0)
              # there was already a rising edge before,
              # so we can calculate the period
              newTime = Time.now();
              @timePeriod = newTime - oldTime;
              @done = true;
            else
              # if there was no previous rising edge,
              # set the time of this one
              oldTime = Time.now();
            end
          end
          oldState = newState;
        end
      end
    end

    puts("Watcher: done.")
  end

  def isDone()
    return @done;
  end

  def getTimePeriod()
    return @timePeriod;
  end
end

Is there any way at all in ruby to pass by reference? And if not, are there alternatives that would solve this problem?

David Wright
  • 464
  • 6
  • 18
  • @Ven In my test application that worked for now. But I am working with a huge and rather old framework. Considering I might not have the possibility to choose between whether or not the argument is an object, would there be another way? – David Wright Aug 29 '16 at 10:49
  • everything is always an object in Ruby anyway – Ven Aug 29 '16 at 10:50
  • @Stefan The signals are mapped into variables. For example I'm checking if a buzzer is toggling with a certain frequency. The state whether or not it is buzzing is stored in a global variable. But there are other values I need to measure the frequency of as well, which is why I want to avoid reading directly from the global variable. – David Wright Aug 29 '16 at 10:52
  • @Ven Well in that case I don't really have a choice regarding using an object, do I? Since there is no other option... – David Wright Aug 29 '16 at 10:53
  • _"use an object"_ and _"everything is always an object"_ is somehow contradictory. Ven probably means that you should use a _mutable_ object. – Stefan Aug 29 '16 at 11:10
  • This brings me back to my previous question: If I do not have any power over whether or not this is a mutable object, is there a possibility to implement it for immutable objects? – David Wright Aug 29 '16 at 11:59
  • No. Ruby is pass-by-value, which means references are immutable. (Well, obviously they are mutable *within* the scope, since you can assign to them after all, but they don't mutate the caller's scope.) If the object you get passed is immutable, too, then there is nothing you can do. The only possibilities for mutation in Ruby are mutating the reference (by assignment) or asking an object to mutate itself (by calling methods on it). You can return an updated value from your method and have the *caller* assign that to the reference, you can wrap the immutable object in a mutable one and mutate … – Jörg W Mittag Aug 29 '16 at 13:21
  • … *that*, but if you can't change anything, then you are stuck. – Jörg W Mittag Aug 29 '16 at 13:22

1 Answers1

10

Ruby is strictly pass-by-value, which means references in the caller's scope are immutable. Obviously they are mutable within the scope, since you can assign to them after all, but they don't mutate the caller's scope.

a = 'foo'

def bar(b)
  b = 'bar'
end

bar(a)

a
# => 'foo'

Note, however, that the object can be mutated even if the reference cannot:

a = 'foo'

def bar(b)
  b << 'bar' # the only change: mutate the object instead of the reference
  b = 'baz'
end

bar(a)

a
# => 'foobar'

If the object you get passed is immutable, too, then there is nothing you can do. The only possibilities for mutation in Ruby are mutating the reference (by assignment) or asking an object to mutate itself (by calling methods on it).

You can return an updated value from your method and have the caller assign that to the reference:

a = :foo

def bar(b)
  :"#{a}bar"
end

c = bar(a)

c
# => :foobar

Or you can wrap the immutable object in a mutable one and mutate that mutable wrapper:

a = [:foo]

def bar(b)
  b[0] = :bar
end

bar(a)

a
# => [:bar]

[This is really the same thing as the second example above.]

But if you can't change anything, then you are stuck.

Jörg W Mittag
  • 363,080
  • 75
  • 446
  • 653
  • Or, you could call it "encapsulation actually works". – Jörg W Mittag Aug 29 '16 at 14:03
  • How so? If you have no control whatsoever over the location your data is stored I'd say "working" is a bit far fetched. How would you expect to access your controller's registers without being able to write to or read from certain addresses? – David Wright Aug 29 '16 at 14:13