?

Log in

No account? Create an account
I'm not a beginner... - Lindsey Kuper [entries|archive|friends|userinfo]
Lindsey Kuper

[ website | composition.al ]
[ userinfo | livejournal userinfo ]
[ archive | journal archive ]

I'm not a beginner... [May. 19th, 2007|07:05 pm]
Lindsey Kuper
[Tags|]

I just make mistakes like one.

So, suppose you've got an object, like this, with some stuff in it:

my $example_object = {
          'key1' => 'foo',
          'key2' => 'bar',
        };

Let's say you want to write a function that'll make a new object just like the first one, but with a couple of the values changed, like this:

sub changeObject {
	my ($obj) = @_;
	
	my $new_obj = $obj;

	$new_obj->{key1} = 'baz';
	$new_obj->{key2} = 'quux';

	logChange($obj, $new_obj);
	
	return $new_obj;
}

Say logChange() is a function that compares its arguments $obj and $new_obj and prints a useful message if they're different, like this:

sub logChange { 
	my ($obj, $new_obj) = @_;
	
	foreach (keys %$obj) {
	      if ($obj->{$_} ne $new_obj->{$_}) {
	            print STDOUT "$obj->{$_} was changed to $new_obj->{$_}\n";
	      }
	}
}

(I know some of you are nodding knowingly right about now.) At this point, you ought to be able to call changeObject($example_object); and it should tell you something like

bar was changed to quux
foo was changed to baz

, because that's what you changed them to. Right?

Wrong! Can you spot the mistake? If it takes you less than an hour, you're doing better than I did:

The problem is that $new_obj and $obj are both just pointers to an anonymous hash. You changed the values in the hash that $new_obj points to, but $obj is pointing to the same one. So comparing $obj and $new_obj won't help you.

Instead, you have to dereference $obj and instantiate a new hash:

sub changeObject {
	my ($obj) = @_;
	
	# gaaaaaaaaaaahhhh.
	my %new_obj = %$obj;
	my $new_obj = \%new_obj;
	
	$new_obj->{key1} = 'baz';
	$new_obj->{key2} = 'quux';

	logChange($obj, $new_obj);
	
	return $new_obj;
}

And now you get the log message you're looking for!

bar was changed to quux
foo was changed to baz

This problem stumped me for way too long today. I just couldn't figure out how the change could be happening and not be getting logged. The real code was more involved than this, but not by much.

On the plus side, I think I finally and suddenly understand pointers. I wish this could've happened, oh, four years ago, but I'll take what I can get.

Hi ho.

Now I think I will finish my coffee and go rumble. Yay!

LinkReply

Comments:
[User Picture]From: pmb
2007-05-20 04:10 am (UTC)
On the plus side, I think I finally and suddenly understand pointers. I wish this could've happened, oh, four years ago, but I'll take what I can get.

Pointers are subtle, because they are both more and less than you expect them to be. I've been told that my advice long ago was that drinking aided in understanding them. There's even a ranking system, used mostly only by undergrads who have yet to have the that epiphany, in which programmers are classified as "one star", "two star", etc, based on the C types of their programs and whether you could find a line like int **c; or one like int ***c;

It's only with C++ and Java that it's possible for people to be zero-star programmers, which you can tell by the name is clearly not a very macho category to be in.
(Reply) (Thread)
[User Picture]From: pixelherder
2007-05-20 07:06 am (UTC)
There's a pretty good page on three-star C programmers in the C2 Wiki.
(Reply) (Parent) (Thread)
[User Picture]From: lindseykuper
2007-05-21 03:35 am (UTC)
Wake up at the crack of noon and head out on the road
Get lost for an hour while we're looking for the show
Tear the roof off every night and load back in the van
Find a place to sleep before the whole thing starts again

We keep making records
We keep writing songs
We are happy hiding underneath the underground
We don't play arenas
We only play in bars
We don't need to pay someone to plug in our guitars
We're zero stars
(Reply) (Parent) (Thread)
From: (Anonymous)
2007-05-22 01:26 am (UTC)

Bad little languages

If you are having to clone objects everywhere you are either doing something wrong or your language is busted.

Everyone has neglected the Evil Empire by not mentioning that C# is the only language that has cleaned up these issues. Explicit ref and out parameter passing as well as explicit method overrides and virtual declarations, not to mention automatic boxing/unboxing of primitives fix alot of problems.

When I worked with Lee Boynton, one of the original Oak and Java team designers he explained that alot of the disconnects in Java were because Gosling wanted performance, hence things like arrays that are not really objects or primitives, C# fixed all that. Thats where I learned not to be a snob or jihadist about languages, except for the ones that really suck...if the guy who helped design Java could appreciate the competitions achievement that was the mark of a true engineer.
(Reply) (Parent) (Thread)
[User Picture]From: cerulicante
2007-05-20 04:57 am (UTC)
I see $example and I read "SEXAMPLE."


This is why I am not allowed near computer languages.
(Reply) (Thread)
[User Picture]From: lindseykuper
2007-05-21 02:01 am (UTC)
It just occurred to me yesterday that the "$" in Perl variable names works as a mnemonic for "scalar".
(Reply) (Parent) (Thread)
[User Picture]From: royhuggins
2007-05-20 05:34 am (UTC)
oof. I wouldn't kick yourself that much on this one. I think one of the reasons why "Perl is bad" is because it's easy to write confusing code.

The reason this stumped me is that I forgot that "$new_obj" is significantly different from "%new_obj" and both are different ways to access the same internal structure. I would think this is as much a problem with obfuscated Perl as with your thought process.
(Reply) (Thread)
[User Picture]From: pixelherder
2007-05-20 07:31 am (UTC)
I wouldn't blame this one on Perl, though. Languages like Python, Java and Scheme -- to name just a few -- will exhibit this same behaviour just as easily. As I rule of them, I assignments with any types beyond basic int, bool, float, etc. tend to be suspect. Basically, it's a question of whether it's a value type or a reference type. Assigning value types to each other is always okay. Assigning reference types can be dangerous. The problem with not teaching people about pointers and sticking with all these languages that try to do without explicit is that one still has to know what's a reference type and what's a value type and why that distinction is important.

My usual strategy for avoiding this type of problem is to borrow from the functional programming book and strive to make all of my data structures as immutable as possible.
(Reply) (Parent) (Thread)
(Deleted comment)
[User Picture]From: lindseykuper
2007-05-20 10:43 am (UTC)
Yeah, I'm with Andrew and Wren: this isn't specific to Perl.
(Reply) (Parent) (Thread)
(Deleted comment)
[User Picture]From: lindseykuper
2007-05-21 02:09 am (UTC)
Of course, Perl's turning @ and % into $ when you use the [] or {} accessors also tends to confuse things for many folks.)

Gawd, I hated that when I started with Perl. I know that @foo[0] is a scalar; there's nothing else it could be. $foo[0] is redundant. "better written as", my ass. Of course, now I've been knee-deep in Perl long enough that @foo[0] looks wrong. *whacks side of head a few times*
(Reply) (Parent) (Thread)
(Deleted comment)
[User Picture]From: lindseykuper
2007-05-22 01:41 am (UTC)
Interesting! I'm not buying their "inspired by natural language" justification for the current variance, though. "the apples", not "these apples", is the plural counterpart of "the apple". "this apple" would be the singular counterpart of "these apples".

Anyway, I'm kind of hoping to not be writing new Perl anymore by the time Perl 6 comes along.
(Reply) (Parent) (Thread)
[User Picture]From: jes5199
2007-05-20 10:59 am (UTC)
alright! cool new feature in Lindsey.

also: let me show you the problem line in ruby, just for comparison:

#bad
new_obj = obj;

#good
new_obj = obj.dup; # "duplicate"


this is a mistake that I still make.
(Reply) (Thread)
From: freyley
2007-05-20 07:18 pm (UTC)
Huh. I like that better than Python, at least on the surface.


import copy

new_obj = copy.deepcopy(obj)


And you can define __copy__ and __deepcopy__ methods on the object which define how copy.copy and copy.deepcopy work on them.

I do remember, when I was programming in Python on a regular basis, looking up copy.deepcopy numerous times.
(Reply) (Parent) (Thread)
[User Picture]From: jes5199
2007-05-20 07:53 pm (UTC)
oh. my example is shallow copy (same as the perl code)

ruby doesn't implement deep-copy in the language, by default ... there's a workaround:

class Object
def deep_clone
Marshal::load(Marshal.dump(self))
end
end

and now you've got (slow) deep copying on every object that can be pickled:

new_obj = obj.deep_clone
(Reply) (Parent) (Thread)
[User Picture]From: lindseykuper
2007-05-21 03:24 am (UTC)
Hm. Yeah, I guess a robust deep copy method would have to instantiate a new object with all the same fields as whatever its argument points to, copy all the fields, and then call itself recursively on any of those fields that are themselves pointers. That is, it'd have to do the dereference-and-then-assign trick at every level. Right?

(Not that logChange() as written would work, anyway, on anything more deeply nested than $example_object.)
(Reply) (Parent) (Thread)
[User Picture]From: jes5199
2007-05-21 04:57 am (UTC)
yes. exactly. the language designers for both ruby and perl have declared that to be too complicated to implement in the language. (which means that we do it ourselves when we need it, and introduce even more bugs.)
(Reply) (Parent) (Thread)
[User Picture]From: lindseykuper
2007-05-21 05:57 am (UTC)

so I never forget

use Data::Dumper;

my $deeper_object = {
	'key1' => 'foo',
	'key2' => {
		'key2_1' => 'bar',
	},
};

my %new_deeper_object = %$deeper_object;
my $new_deeper_object = \%new_deeper_object;

$new_deeper_object->{key1} = 'Yes!';			# pointed at once
$new_deeper_object->{key2}->{key2_1} = 'I get it!';	# pointed at twice

print STDOUT Dumper $new_deeper_object;

print STDOUT Dumper $deeper_object;

lagniappe:~/Desktop lkuper$ perl deep_example.pl 
$VAR1 = {
          'key2' => {
                      'key2_1' => 'I get it!'
                    },
          'key1' => 'Yes!'
        };
$VAR1 = {
          'key2' => {
                      'key2_1' => 'I get it!'
                    },
          'key1' => 'foo'
        };

(Reply) (Parent) (Thread)
[User Picture]From: jes5199
2007-05-21 06:03 am (UTC)

Re: so I never forget

this example made me think of an evil hack that's analogous to the ruby solution:

my $new_deeper_object = eval( Dumper($deeper_object) );
(Reply) (Parent) (Thread)
[User Picture]From: lindseykuper
2007-05-21 07:07 am (UTC)

Re: so I never forget

Scrapey!

The Internet is telling me to use Storable.
(Reply) (Parent) (Thread)
(Deleted comment)
From: freyley
2007-05-21 04:58 am (UTC)
Oh. Well, copy.copy is the equivalent, and I can't really speak for it being faster than marshal/unmarshalling, but it's at least built in (well, okay, in the standard library.)

Ah well.
(Reply) (Parent) (Thread)
[User Picture]From: lindseykuper
2007-05-23 04:44 pm (UTC)
I thought my second example was deep copy, too, at first, but really it's just a slightly less shallow copy than the first example.

I'm think I'm going to start referring to the simple my $new_obj = $obj; pointer assignment as "flat copy".
(Reply) (Parent) (Thread)
From: freyley
2007-05-24 05:54 am (UTC)
sure. or pointered dupe. Nope, yours is better.
(Reply) (Parent) (Thread)
[User Picture]From: lindseykuper
2007-05-21 03:29 am (UTC)
Thanks! It was a rush of enlightenment. Reading it over now, I'm like, "Well, obviously."
(Reply) (Parent) (Thread)
From: boojum
2007-05-20 05:35 pm (UTC)
This is one of the reasons I wish "const" were a more common language feature. It really helps you think about which things you want changing and which you don't, and then the compiler will help support you in this.
(Reply) (Thread)
[User Picture]From: jes5199
2007-05-20 07:56 pm (UTC)
I did an experiment that overloaded the hash operators in perl to explode if you were changing or accessing data from the wrong namespace. it would catch this sort of thing, I don't know why it's not a standard perl idiom to boobytrap your data structures.
(Reply) (Parent) (Thread)