1

The purpose is to only ammend the hashref argument being passed to the function, but not modify the original hashref.

{
    my $hr = { foo => q{bunnyfoofoo},
               bar => q{barbiebarbar} };

    foo( { %$hr, baz => q{bazkethound} } );
}

sub foo {
   my $args = shift // {};
}

I think the above would work, but am not sure there is a better way (other than modifying the orginal ref before passing).

vol7ron
  • 40,809
  • 21
  • 119
  • 172
  • this works as expected, I don't see the problem – John Corbett Aug 07 '12 at 15:15
  • Yep, I do this pretty frequently. You can have the same keys in the amendment as well, which will override the values in `%$hr` without modifying `$hr`. If you want to prevent your amendments from overriding, make `%$hr` come last inside the anonymous `{ ... }`. – zostay Aug 07 '12 at 15:18
  • 2
    A slightly less orthodox solution would be to simply pass a hash by value, instead of as a hashref (if you don't expect to modify it). Less clunky syntax all around. – DVK Aug 07 '12 at 15:35
  • I suspected this might be one of the only ways, but it seemed bassackwards to dereference and combine into a new ref – vol7ron Aug 07 '12 at 17:42

2 Answers2

1

Whatever the syntax you choose, it will still boil down to shallow or deep (if you need to modify inside function) copy of original.

If your function and everything inside it is guaranteed not to modify hash, then assigning this key before call and restoring previous value or deleting it after the call could be enough as well.

Oleg V. Volkov
  • 21,719
  • 4
  • 44
  • 68
1

Your code is as simple as it can be. You need to pass a hash and you don't want to modify the existing hash, so you need to create a new hash. That's what { } does.

The underlying problem is foo takes a hash ref instead of a key-value list.

sub foo {
   my %args = @_;
   ...
}

You can still do everything you did before:

foo( foo => 'bunnyfoofoo', bar => 'barbiebarbar' );

my $hash = {
   foo => 'bunnyfoofoo',
   bar => 'barbiebarbar',
};
foo( %$hash, baz => 'bazkethound' );

There are two advantages.

  1. You don't have to use { } all over the place.

  2. You can modify the argument safely.

    This is buggy:

    sub f {
       my $args = $_[0] // {};
       my $foo = delete($args->{foo});
       my $bar = delete($args->{bar});
       croak("Unrecognized args " . join(', ', keys(%$args)))
          if %$args;
       ...
    }
    

    This is ok:

    sub f {
       my %args = @_;
       my $foo = delete($args{foo});
       my $bar = delete($args{bar});
       croak("Unrecognized args " . join(', ', keys(%args)))
          if %args;
       ...
    }
    
ikegami
  • 367,544
  • 15
  • 269
  • 518
  • Very nice. But aren't there negatives to passing hashes as opposed to hashrefs? – vol7ron Aug 07 '12 at 17:39
  • @vol7ron, (You're passing a list, not a hash; it's impossible to pass a hash.) Nope. I thought it might be a tiny bit slower, but it's actually unnoticeably faster. (0.000,000,040s = 40ns on my machine for `foo => "foo", bar => "bar"`) – ikegami Aug 07 '12 at 18:17
  • @vol7ron, The list approach would be slower than hash ref if you start with a hash (e.g. that you got from a config file) and you didn't need to add or remove from the hash, but that's actually quite rare and usually done a fix number of times (making it a ineffective area for optimisation). – ikegami Aug 07 '12 at 18:21
  • Just making sure we're on the same page. You're still passing a hash, only it does the conversion of a hash to a list, right? Wow, `40ns`, I'm curious how much longer it would take if the hashref was bigger; for instance, if it contained a DBI and CGI handle. – vol7ron Aug 07 '12 at 18:41
  • I also suppose instead of `$args = $_[0] // {};` you could do `$args = {@_};` if passing a list. For some reason I stick to references, as opposed to hashes (*i don't know why*). – vol7ron Aug 07 '12 at 18:53
  • @vol7ron, I compared `foo({foo => "foo", bar => "bar"})` to `foo(foo => "foo", bar => "bar")`. The size of the values doesn't matter (because the elements of `@_` are aliases), but the number of elements could make a minute differences (since it changes the size of `@_`). – ikegami Aug 07 '12 at 18:53
  • `$args = {@_}` means you need a bunch of extra symbols (`->`) in your code for no reason. You imply some sort of consistency reason, but there isn't any. It's silly to avoid named hashes because you also use anonymous hashes *for other reasons*. – ikegami Aug 07 '12 at 18:56
  • yeah the first point is what I was thinking just the number of elements, but not necessarily how deep, since they should be references themselves. As for the second point, I don't mind `->`, I think I do it because I prefer the format, especially as the hash gets deeper. Also, when returning values from a subroutine, its better to return a reference, so I don't have to remember to deref. – vol7ron Aug 07 '12 at 19:41
  • @vol7ron, Arguments are scalars. Depth is not a factor. – ikegami Aug 07 '12 at 19:44
  • @vol7ron, As for functions with huge list of arguments, it would be fair to say that the time the function spends processing the arguments will eclipse any performance differences of list vs hash ref. So the answer is still "No, there's no downsides". – ikegami Aug 07 '12 at 19:46
  • what do you have to say about something like this instead: `my $args = ref $_[0]?shift:{@_};`, which should allow for passing argments as `foo(a=>1)` or `foo({a=>1})` and always receive it as a ref? – vol7ron Aug 08 '12 at 19:20
  • @vol7ron, I would say "why?!" Aside from the useless complexity, it actually adds some of the downsides of the ref version to the list version. (`my %args = ref($_[0]) ? %{ shift } : @_;` would avoid that, but it would make things complicated.) I just don't see the reason to dirty up the caller with a hash construction, so I don't see any reason to add support for it either. – ikegami Aug 08 '12 at 19:27
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/15091/discussion-between-vol7ron-and-ikegami) – vol7ron Aug 08 '12 at 20:58