6

I'm learning Ruby (2.0) and this just surprised me:

s = "1234"
s =~ /\d+/
$&  ==> "1234"       # as expected, $& contains the matched string
$&.slice!(-2..-1)    # should mutate string
$&  ==> "1234"       # what?
s.slice(-2..-1) 
s  ==> "12"          # as expected

The slice! method is supposed to mutate the string. Other mutator methods behave in the same way. My questions: why is this not throwing an error, which is what I expect when a function can't do what it says it will do? Is this documented somewhere? Is there a rationale?

Update

So, I see that $& is not acting like a global variable. Each reference to it gives a new object, as if it's really a no-arg function:

irb> $foo = "1234"
=> "1234"
irb> $foo.object_id
=> 70205012205980
irb> $foo.object_id
=> 70205012205980   # the same
irb> $&.object_id
=> 70205003531300
irb> $&.object_id   
=> 70205011619040   # different object

So... my question becomes: is this simply "magic" from the interpreter, or is $& actually a no-arg function just as I could define in Ruby using def ... end? And, how could I tell the difference? In Python I could refer to a function foo by just using it's name:

>>> foo
<function foo at 0x10d3117d0>

Is there way to do this in Ruby? I could then look at what $& "really" is (if it's not magic).

Andrew Grimm
  • 78,473
  • 57
  • 200
  • 338
Rob N
  • 15,024
  • 17
  • 92
  • 165
  • 1
    Why would you want to change `$&`? To lie to subsequent code about what was matched? Notice that `$&` doesn't have any "setters": `$&.methods.grep(/=/) => [:===, :=~, :<=>, :==, :!=]` – the Tin Man Feb 28 '13 at 17:39
  • I'm parsing something and the `slice!` method provided a convenient way to pick about the string as I wished, in one line. But, that is irrelevant. The object has type String, and therefore it should respond to String methods. It does have mutators (methods that end in !), and if an object can't respond to its method, then it should throw an exception. – Rob N Feb 28 '13 at 17:52
  • 1
    "The object has type String". The object *returned* is a String. Assign that to something and you can munge it to your heart's content. Changing `$&` would be bad. Changing what `$&` returns is fine. – the Tin Man Feb 28 '13 at 17:55
  • $& is a variable, not a function. A variable doesn't return anything. – Rob N Feb 28 '13 at 17:58
  • 2
    The `$&` global (and many others) are read-only - http://ruby.runpaint.org/globals – maerics Feb 28 '13 at 18:07
  • Okay, the Tin Man indirectly answered my question. I will update the question, because I'm still confused about something... – Rob N Feb 28 '13 at 18:17
  • 1
    "In Python I could refer to a function foo by just using it's name: `foo`". In ruby `foo` would execute the method foo. To refer to a method do this: `method(:foo)`. – steenslag Feb 28 '13 at 19:21

3 Answers3

5

The Ruby C API includes hooked and virtual variables. From README.EXT:

You can defined hooked variables. The accessor functions (getter and setter) are called on access to the hooked variables.

void rb_define_hooked_variable(const char *name, VALUE *var,
           VALUE (*getter)(), void (*setter)())

If you need to supply either setter or getter, just supply 0 for the hook you don't need. If both hooks are 0, rb_define_hooked_variable() works just like rb_define_variable().

The prototypes of the getter and setter functions are as follows:

VALUE (*getter)(ID id, VALUE *var);
void (*setter)(VALUE val, ID id, VALUE *var);

Also you can define a Ruby global variable without a corresponding C variable. The value of the variable will be set/get only by hooks.

 void rb_define_virtual_variable(const char *name,
            VALUE (*getter)(), void (*setter)())

The prototypes of the getter and setter functions are as follows:

VALUE (*getter)(ID id);
void (*setter)(VALUE val, ID id);

$& is an example of a virtual variable, it is defined in re.c, the corresponding getter is last_match_getter and there is no assocoated setter.

So $& is, in a sense, a no-arg function, only it is implemented in C. You can’t (as far as I’m aware) define your own virtual globals like this in pure Ruby (you can if you create your own C extension).

matt
  • 78,533
  • 8
  • 163
  • 197
2

Since the purpose of $& is to tell you what has been matched, it would make sense that matching should be the only operation that can update it.

jeconner
  • 398
  • 1
  • 2
  • 10
1

This is an interesting question. jeconner's answer and many comments to the question are just saying that it is not useful to do this, but that is not answering the question. Whether or not that should be done in practical code, the OP's question still remains.

The answer to the question is that, this looks like a feature for read-only strings. When you apply a destructive operation to a read-only string, it returns a different string object. And I agree with the OP that it is strange. It should rather raise an error.

Unless someone comes up with a convincing explanation, I suggest you to ask it on Ruby core, and/or request a feature to raise an error instead.

sawa
  • 165,429
  • 45
  • 277
  • 381