1

In PHP I can use & to assign the reference of a variable to another variable as seen in the first code snippet below.

See PHP "Returning References" documentation for some more context... http://www.php.net/manual/en/language.references.return.php

PHP Code:

<?php
  $tree = array();
  $tree["branch"] = array();
  $tree["branch"]["leaf"] = "green";

  echo '$tree: ';
  var_dump($tree);

  $branch = &$tree["branch"];

  $branch = "total replacement!";

  echo '$tree: ';
  var_dump($tree);
?>

PHP's output:

$tree: array(1) {
  ["branch"]=>
  array(1) {
    ["leaf"]=>
    string(5) "green"
  }
}
$tree: array(1) {
  ["branch"]=>
  &string(18) "total replacement!"
}

Trying to do this in Ruby I did:

tree = {}
tree["branch"] = {}
tree["branch"]["leaf"] = "green"

puts "tree: #{tree.inspect}"

branch = tree["branch"]

branch = "total replacement!"

puts "tree: #{tree.inspect}"

Which output:

tree: {"branch"=>{"leaf"=>"green"}}
tree: {"branch"=>{"leaf"=>"green"}}

Now, while this straight assignment does not work in Ruby, modification of the object does:

Ruby Code (Continued):

branch["lead"] = "red"
puts "tree: #{tree.inspect}"

Ruby's Output:

tree: {"branch"=>{"leaf"=>"red"}}

So, I'm left wondering if there is a way to find the "parent" of an object so that I might modify it like I've done with branch["leaf"].

Author Edit:

While one can't change a hash reference to any other variable through assignment (e.g., x = y), one can modify the existing object through its methods. Using this approach one can pseudo-assign a new hash to the branch variable using .replace() as seen below...

Replacement Instead of Assignment:

tree = {}
tree["branch"] = {}
tree["branch"]["leaf"] = "green"

puts "tree: #{tree.inspect}"

branch = tree["branch"]

branch.replace({"leaf" => "red", "twig" => "brown"})

puts "tree: #{tree.inspect}"

Output:

tree: {"branch"=>{"leaf"=>"green"}}
tree: {"branch"=>{"leaf"=>"red", "twig"=>"brown"}}
Daniel Doezema
  • 1,592
  • 10
  • 13

2 Answers2

1

A Variable Can Store a Key

Rather than trying to use indirect references or Kernel#eval, the Ruby way is to use a variable or expression to store or define the key you need. This isn't quite the same as your PHP code, but you can certainly use Ruby's native Hash methods to find the key for a given value.

Finding the Key You Want

I'm left wondering if there is a way to find the "parent" of an object so that I might modify it like I've done with branch["leaf"].

Consider the nature of a hash: each key in a hash must be unique, although keys for nested hashes can be the same as a key in a parent or sibling hash. For example:

# Keys for nested hashes can be the same.
tree = {
  branch1: { leaf: 'green' },
  branch2: { leaf: 'red'   },
}
#=> {:branch1=>{:leaf=>"green"}, :branch2=>{:leaf=>"red"}}

# Duplicate keys; last one "wins."
tree = {
  branch: { leaf: 'green' },
  branch: { leaf: 'red'   },
}
#=> {:branch=>{:leaf=>"red"}

On the bright side, that means that any given level of your hash will have exactly one key that matches what you want. There are any number of ways to find it. One example is using Hash#rassoc to return the key you want:

tree.rassoc( {leaf: 'red'} ).first
#=> :branch2

Other methods like #select, #each_pair, and so forth may also be useful, depending on your needs and the semantics of what you're trying to express.

Using the Key Name

Once you have the key, you can use that key to modify the associated value for that key. For example:

tree = {
  branch1: { leaf: 'green' },
  branch2: { leaf: 'red'   },
}
key = tree.rassoc( {leaf: 'red'} ).first
tree[key] = { leaf: 'blue' }
tree
#=> {:branch1=>{:leaf=>"green"}, :branch2=>{:leaf=>"blue"}}
Todd A. Jacobs
  • 81,402
  • 15
  • 141
  • 199
  • I'm going to accept this answer it's very thorough. It also gave me the idea of "Instead of assignment, maybe one can monkey patch a way to clear the hash and replace it with a new hash's contents..." -- I then jumped over to the Hash docs and found the `.replace()` method does just that :) (An example of usage has been edited into question above.) – Daniel Doezema May 23 '13 at 13:13
0

For starters, Ruby is pass by reference. Think of it this way - this line:

branch = tree["branch"]

is creating a second reference to the underlying hash you created here:

tree["branch"] = {}

Those references are the:

  1. local branch variable.
  2. hash value in the tree hash for key branch.

By assigning a different value to the local variable branch, you're removing one reference, e.g. you're changing the reference holder not the reference itself. In Ruby you wouldn't expect the reference in the hash to change when a totally separate reference changed.

Some objects support operations that allow you to update them in-place, hashes for instance, but there is no facility I'm aware of to perform cascading reference changes like you're describing (e.g. change all references to an object to some other object).

Community
  • 1
  • 1
Dave S.
  • 6,349
  • 31
  • 33
  • 2
    Ruby is *not* pass-by-reference. If it were, the OP's code would have worked. Ruby is pass-by-value. Always. No exceptions. Here's a piece of code you can run to check whether Ruby (or any other language) is pass-by-value or pass-by-reference `def foo(bar) bar = 'reference' end; baz = 'value'; foo(baz); puts "Ruby is pass-by-#{baz}" # Ruby is pass-by-value` – Jörg W Mittag May 23 '13 at 02:52
  • I've seen this counterpoint before, in this thread here: http://www.ruby-forum.com/topic/41160#8451 (it's further up, but this link is to the main reason I said "pass-by-ref"). Do you think that is incorrect, or is this just a matter of "pass-by-ref" implies OUT parameters while "pass-by-value" prohibits them? I just notice you made the same argument in the SO question I linked! :) I guess this question never dies. – Dave S. May 23 '13 at 03:38
  • It depends on how you look at it. It's pass-by-reference in the sense that it sends the original object and not a copy. The original object can be affected inside the method, which couldn't be possible in true pass-by-value. – pguardiario May 23 '13 at 03:45
  • 1
    @JörgWMittag: pass-by-value but the value itself is a reference (`def f(x) x.push(6) end; a = [ ]; f(a)` for example), that's the common source of confusion. – mu is too short May 23 '13 at 06:34
  • @pguardiario: the fact that you can mutate the object is completely irrelevant. That's a question of mutable state which is orthogonal to pass-by-reference vs. pass-by-value. Ruby is not a pure functional language, and nobody claimed it to be. Pass-by-reference is about being able to modify *references* (i.e. variables), not objects. Ruby doesn't allow you to do that. – Jörg W Mittag May 23 '13 at 10:52
  • @muistooshort: Unfortunately, that's a misleading way to put it, since the word "reference" in your statement has a very different meaning than in the word "pass-by-reference". I think "pass-by-value where the value is a pointer" would be a better way to put it, although unfortunately the word "pointer" is also pretty loaded. Barbara Liskov invented the term "call-by-object-sharing" (also "call-by-sharing" or "call-by-object") specifically for this, but nobody seems to use it. – Jörg W Mittag May 23 '13 at 10:55
  • Well, in the c sense I would say that you can modify a reference in a ruby method. Therefore pass-by-reference. But really it's sort of halfway between the two. – pguardiario May 23 '13 at 11:48
  • @JörgWMittag: No, that's not a misleading way to put it, the ambiguity in the terminology is the problem. The meaning of *reference* is context dependent and that can be misleading if you don't understand the context. – mu is too short May 23 '13 at 17:52
  • @pguardiario: C is pass-by-value, *never* pass-by-reference. You can pass pointers, but those are still passed by value. C++ does support pass-by-reference but C doesn't. Ruby is pass-by-value exactly like C, if you were always passing pointers. – Jörg W Mittag May 24 '13 at 01:17
  • @muistooshort: Yeah, sorry, that's what I meant. The problem is that someone who doesn't understand what "pass-by-reference" means probably also doesn't understand what "reference" means in the different contexts and thus might conclude that pass-by-reference and pass-reference-by-value are the same thing, which they are most definitely not. Just look at the answers and comments on this very question for an example of the myriad of ways people confuse the two. – Jörg W Mittag May 24 '13 at 01:20