Thread

Posted on Wed Nov 30 02:08:48 2005 by je44ery
How To Deal With Arrays Passed Into Objects During Initialization
Sorry to post this simple how-to question, but this is my first time using Perl as anything other than a glorified awk.

I am passing in an array ref during object creation:

use warnings; use strict; use Hashtest; my $arrref = [ "hunger", "pain", "misery", ]; my $newobj = Hashtest->new( 'test1' => $arrref); exit 0;

Now, in order to encapsulate my data, I need to make a copy of this array inside the object, and modify that instead. It is doing this simple task that is confounding me. Here is my attempt-of-the-moment with my debug statments-of-the-moment:

package Hashtest; use strict; use warnings; use Object::InsideOut; my @test1 :Field; my %init_args :InitArgs = ( 'TEST1' => { 'Regex' => qr/^test1/i, 'Type' => 'ARRAY', # 'Field' => \@test1, }, ); sub _init :Init { my ($self, $args) = @_; my $tmpref = $args->{TEST1}; $test1[$$self] = @$tmpref; my $tmparr = $test1[$$self]; print "MADE IT = $tmparr\n"; print "MADE IT =? $test1[$$self]\n"; } 1;
Direct Responses: 1425 | Write a response
Posted on Wed Nov 30 20:11:34 2005 by jdhedden in response to 1420
Re: How To Deal With Arrays Passed Into Objects During Initialization
> this is my first time using Perl as anything other than a glorified awk.

Well, I hope this is a good beginning for you. Thanks for taking a look at Object::InsideOut.

First, let's tackle the following bug:

$test1[$$self] = @$tmpref;

An array in a scalar context returns its 'length'. Thus, you end up storing the number 3, which is not what you want. What you want is to store the array ref itself in the field. Thus, for the fix, you have several options: You can either store the input directly with:

$test1[$$self] = $tmpref;

or you can make a copy, and then store that:

my @copy = @$tmpref; $test1[$$self] = \@copy;

Since your input is assigned to $arrref, the latter might be preferable if your code will be making any changes to the the array ref's contents via $arrref. If not, the former is simpler.

However, the option that is preferable to the above is to use the 'set' method:

$self->set(\@test1, $tmpref);

This has the same effect as the first example above with one crucial difference: If your code is being used in a threads::shared environment, the 'set' method handles all the details of making a shared copy of the data you're going to store in your fields.

Now, as to your question, I'll be working with a slightly modified version of your example. Most notably, I'll be using Test::More because it makes it easy to show test results.

The first thing you should try is to let Object::InsideOut do the menial work for you. Consider your :Init subroutine: All it does is store the input into the field. By uncommenting the 'Field' parameter in your :InitArgs, you can have Object::InsideOut automatically do this for you such that you don't even need an :Init subroutine at all:

use warnings; use strict; use Test::More 'no_plan'; package Hashtest; { use Object::InsideOut; my @data :Field('Standard' => 'data'); my %init_args :InitArgs = ( 'DATA' => { 'Regex' => qr/^data/i, 'Type' => 'ARRAY', 'Field' => \@data, }, ); } package main; my $input = [ "hunger", "pain", "misery" ]; my $obj = Hashtest->new('data' => $input); my $output = $obj->get_data(); is_deeply($input, $output => 'Equality for all'); exit(0);

Note that I added accessor generation parameters to the :Field attribute so that the data could be accessed via the object.

Here's the same example with the :Init subroutine added back in, and using the 'set' method as discussed above:

use warnings; use strict; use Test::More 'no_plan'; package Hashtest; { use Object::InsideOut; my @data :Field('Standard' => 'data'); my %init_args :InitArgs = ( 'DATA' => { 'Regex' => qr/^data/i, 'Type' => 'ARRAY', }, ); sub _init :Init { my ($self, $args) = @_; if (exists($args->{'DATA'})) { $self->set(\@data, $args->{'DATA'}); } } } package main; my $input = [ "hunger", "pain", "misery" ]; my $obj = Hashtest->new('data' => $input); my $output = $obj->get_data(); is_deeply($input, $output => 'Equality for all'); exit(0);

Since you did not make the 'DATA' parameter mandatory, the :Init subroutine needs to check for existance prior to trying to store the data.

Hope this answered your question adequately.

Direct Responses: 1427 | 1428 | Write a response
Posted on Thu Dec 1 01:30:12 2005 by je44ery in response to 1425
Re: How To Deal With Arrays Passed Into Objects During Initialization
First and foremost, thank you very much for responding since my problems are not specifically about Object::InsideOut. I appreciate it much.

Second, you are helping a lot, and again, thank you. I do have some comments, tho. Maybe a question or two, I'm not sure yet.

I have two different responses to your message. I'll be posting them separately and with probably a lot of time between them as I experiment with the code.

Oh, yeah, sorry for my sad code examples the first time. I had tried tons of stuff that day, and I should have cleaned up my examples before posting.

On to my comments. I had originally tried using the :InitArgs field parameter to set the value, but all it does is copy the address of the array (which makes perfect sense), not the array itself. Since code outside the object or class would be able to modify the array, that violates the data encapsulation concept. The main reason I'm using Object::InsideOut is for data encapsulation. Therefore, I can't use :InitArgs because I need a separate copy of the array being pointed-to for use inside the object. I can't modify the pointed-to array and claim data encapsulation.

Here is how I proved this:

$ cat Arraytest.pm package Arraytest; use strict; use warnings; use Object::InsideOut; my @data :Field('Std' => 'data'); my %init_args :InitArgs = ( 'DATA' => { 'Regex' => qr/^data/i, 'Type' => 'ARRAY', 'Field' => \@data, }, ); sub prove_it { my ($self, $args) = @_; print "INSIDE OBJECT = $data[$$self]\n"; } 1;

$ cat arrayt.pl #!/usr/bin/perl -w use warnings; use strict; use Arraytest; my $input = [ "hunger", "pain", "misery", ]; my $newobj = Arraytest->new( 'data' => $input); $newobj->prove_it(); print "OUTSIDE OBJECT = $input\n"; exit 0;

Here is the output. You see I have the same address. What I need is a whole new copy of that array.

# ./arrayt.pl INSIDE OBJECT = ARRAY(0x301482c0) OUTSIDE OBJECT = ARRAY(0x301482c0)
Direct Responses: 1431 | Write a response
Posted on Thu Dec 1 01:48:07 2005 by je44ery in response to 1425
Re: How To Deal With Arrays Passed Into Objects During Initialization
OK, this is my second response.

After posting yesterday, I finally figured out the right syntax to copy one array to another using an array reference. Thanks for the syntax, also. Confirmation is nice.

But that still leaves me with another conundrum. Take a look at this. It seems to be what I want.

$ cat Arraytest.pm package Arraytest; use strict; use warnings; use Object::InsideOut; my @data :Field('Std' => 'data'); my %init_args :InitArgs = ( 'DATA' => { 'Regex' => qr/^data/i, 'Type' => 'ARRAY', }, ); sub _init :Init { my ($self, $args) = @_; my $tmpref = $args->{DATA}; my @tmpdata = @$tmpref; $data[$$self] = \@tmpdata; } 1;

I have created a copy of the array, and now I have assigned the object field to point to it. However, since I created the array copy inside of a subroutine and declared it with my, doesn't the actual array that I'm pointing to get garbage collected after that subroutine ends? Won't I rapidly risk pointing to garbage when _init ends?

If that is true, and I don't see why it isn't, then how do I make the array copy itself persistent with the object?
Direct Responses: 1432 | Write a response
Posted on Thu Dec 1 17:53:16 2005 by jdhedden in response to 1427
Re: How To Deal With Arrays Passed Into Objects During Initialization
This is the "standard" way of storing refs inside objects - you just save the reference, and don't make a copy of the complex structure it contains (especially since this might not always be possible as in the case of objects). It's not necessarily a violation of data encapsulation per se, however, in your case you are levying the additional requirement that a copy of this particular data be stored inside the object. This is fine, but you need to handle that aspect yourself, as I illustrated in my previous reply.
Write a response
Posted on Thu Dec 1 17:59:37 2005 by jdhedden in response to 1428
Re: How To Deal With Arrays Passed Into Objects During Initialization
> However, since I created the array copy inside of a
> subroutine and declared it with my, doesn't the actual
> array that I'm pointing to get garbage collected after
> that subroutine ends? Won't I rapidly risk pointing to
> garbage when _init ends?

When you store a reference to a local (i.e., my) variable, the data persists beyond the original scope. See 'perldoc perlref' for more information on this.

Write a response