Masem has asked for the wisdom of the Perl Monks concerning the following question:

I'm writing some module code in which I get data that I want to pass to the end-developer that is from a standard function that returns a list of items in a predefined order, much like how localtime works. Thus, for the end-developer that knows the standard format for this data, they'll probably want to use it as an array format. However, for the inexperienced developers, I'd also like to have that same data available as a hash with understandable names to make it easier for them to grab what data they need.

In otherwords, I'd want the following code to be valid:

my $obj = do_stuff_from_my_module(); my $name = $obj->[2]; # Assume name is the 3rd element # # OR!!! # my $name = $obj->{ name };
Now, if $obj is just a hash, the hash reference will work, but the array reference will give me problems. Of course, just using an array loses the meanful names.

If you look at a module like Net::Servent which trying to offer a similar interface, the names that I'd be using in my hash keys are now moved to method names, something I'm not thrilled about doing. I'd rather have the hash/array to be 'consistent' with the fact that this is just a data structure with no methods associated with it.

One thing that should be pointed out is that none of the names for the name will be solely numbers; this could be useful in distinguishing if the developer wants the array or the hash reference.

Is it possible to create a variable tied as both an array and a hash at the same time? Or is there some other approach one could use for this? Or is this an impossible task in perl?

-----------------------------------------------------
Dr. Michael K. Neylon - mneylon-pm@masemware.com || "You've left the lens cap of your mind on again, Pinky" - The Brain
"I can see my house from here!"
It's not what you know, but knowing how to find it if you don't know that's important

Replies are listed 'Best First'.
Re: Scalar ref acting as simulatenous hash and array?
by dragonchild (Archbishop) on Oct 19, 2001 at 19:29 UTC
    Ok. After some experimentation, I've got the following package:

    Update: Version 2.0

    use strict; package MyTie; my @TIED; sub TIEHASH { my $type = shift; my $index = shift; return undef unless $TIED[$index]; my $self; if ($TIED[$index]) { $self = $TIED[$index]; } else { $TIED[$index] = $self = {}; bless $self, ref($type) || $type; } $self->{HASH} = {}; @{$self->{HASH}}{@_} = @{$self->{ARRAY}}; $self->{ISTIED}{HASH} = 1; return $self; } sub TIEARRAY { my $type = shift; my $index = shift; my $self; if ($TIED[$index]) { $self = $TIED[$index]; } else { $TIED[$index] = $self = {}; bless $self, ref($type) || $type; } $self->{ARRAY} = [ @_ ]; $self->{ISTIED}{ARRAY} = 1; return $self; } sub FETCH { my $self = shift; my $key = shift; return undef unless defined $key; if ($key =~ /\D/) { return undef unless $self->{ISTIED}{HASH}; return $self->{HASH}{$key}; } else { return undef unless $self->{ISTIED}{ARRAY}; return $self->{ARRAY}[$key]; } } sub STORE { my $self = shift; my $key = shift; return undef unless defined $key; if ($key =~ /\D/) { return undef unless $self->{ISTIED}{HASH}; $self->{HASH}{$key} = shift; } else { return undef unless $self->{ISTIED}{ARRAY}; $self->{ARRAY}[$key] = shift; } } sub FETCHSIZE { my $self = shift; return $#{$self->{ARRAY}}; } 1; __END__
    It's obviously not complete, as most of the ARRAY and HASH functions aren't included. They're relatively easy to do, according to the model.

    To create a MyTie, I used the following:

    #!/usr/local/bin/perl use strict; use warnings; use MyTie; my $index = 0; my @blah; tie @blah, "MyTie", $index, (5, 4, 3, 2, 1); my %blah; tie %blah, 'MyTie', $index, qw(zero one two three four); print "$blah[2]\n"; print "$blah{two}\n";
    Now, to extend this so you can have more than one MyTie, you have to decide how you're going to construct this. One possibility is to do the following:
    1. Have a package global of a list in MyTie.
    2. Always construct the array first.
    3. Make the first element in the list some $index in that global array.
    4. pop that index off before putting it into $self->{ARRAY}.
    5. When creating the HASH part, put a index => $index.
    Yet another thing to add is to create the array first, then create the hash by only passing the keys in as a list and creating the hash on the fly. I suspect this is what you want more. (Done in v2.0)

    ------
    We are the carpenters and bricklayers of the Information Age.

    Don't go borrowing trouble. For programmers, this means Worry only about what you need to implement.

Re: Scalar ref acting as simulatenous hash and array?
by davorg (Chancellor) on Oct 19, 2001 at 18:42 UTC

    Sounds to me like pseudohashes would be a perfect solution to this problem.

    But I wouldn't use them as they're deprcated in 5.8 and will be removed in 5.10.

    --
    <http://www.dave.org.uk>

    "The first rule of Perl club is you don't talk about Perl club."

Re: Scalar ref acting as simulatenous hash and array?
by Sidhekin (Priest) on Oct 19, 2001 at 19:47 UTC

    Core module overload allows you to overload dereferencing. If you overload hash dereferencing only, you may have it. Something like this, perhaps:

    { package TwoFaceRefs; use overload '%{}' => \&getByKey; my %idx = qw(name 2); # And probably some more :-) sub getByKey { # Likely faster to return a tied hash, # but you get the idea ... my $obj = shift; return {map{ $_ => $obj->[$idx{$_}] } keys %idx}; } sub dsfmm { # Assuming we get an arrayref: my $obj = do_stuff_from_my_module(); bless $obj, "TwoFaceRefs"; return $obj; } } my $obj = TwoFaceRefs::dsfmm(); my $name = $obj->[2]; # # or # my $name = $obj->{name};

    Update: Comment and (a little) whitespace added.

    Update 2: Oh, unless we make getByKey return a (properly) tied hash, assigning to the fields won't work:
    $obj->{name} = "Sidhekin";
    The original problem merely stated to access the fields, not setting them, but I just wanted to make it clear that this is one problem overloading (on its own) will not solve. (There is no problem overloading and tying together cannot solve. Well, okay. A few.)

    The Sidhekin

Re: Scalar ref acting as simulatenous hash and array?
by clintp (Curate) on Oct 19, 2001 at 19:37 UTC
    If you don't mind stupid AUTOLOAD tricks, you can get rid of the punctuation altogether:
    use strict; package FOO; our($AUTOLOAD); sub new { my $class=shift; my $self; $self={}; # Sometimes you feel like a hash $self=[] # Sometimes you don't. if (int rand 2 == 0); bless $self,$class; } sub AUTOLOAD { my($self,$value)=@_; my($attr)=($AUTOLOAD=~m/::(.*?)$/); return if ($attr=~/^[A-Z]+$/); if ($attr=~/^[_\d]+$/) { print "Called digit accessor\n"; } else { print "Called non-digit accessor\n"; } } package main; my $t=new FOO; $t->_45; # "->45" won't make it through the parser $t->name;
Re: Scalar ref acting as simulatenous hash and array?
by Anonymous Monk on Oct 19, 2001 at 18:52 UTC
    Is there any reason why you can't just have a function available for the inexperienced devs to convert the recieved array into a hash if they aren't happy with using an array?
    eg
    my $obj_array = do_stuff_from_my_module(); my $obj_hash = convert_masems_array_to_a_hash($obj_array); #then the valid code options would be my $name = $obj_array->[2]; # Assume name is the 3rd element # # OR!!! # my $name = $obj_hash->{ name };

    Your other option could be to use a pseudo hash but I haven't really used them myself for anything significant.

      Well, in that case I think it's easier to provide two functions, one which returns the array and one which returns the hash.

      Another idea would be to provide some global way of configuring the module into either returning hashes or arrays.

      -- Hofmator

Re: Scalar ref acting as simulatenous hash and array?
by dragonchild (Archbishop) on Oct 19, 2001 at 19:14 UTC
    From everything I've been reading, you can't tie hashrefs or listrefs, anyways. You could look at trying to tie a GLOB, but I have no idea how you'd go about doing that. And, as I'm typing this, I'm trying to figure it out and it's not looking good. *shrugs*

    I'm also looking at possibly tying a hash and an array together ... but that's also not looking good. *shrugs*

    Update: Check out my second response...

    ------
    We are the carpenters and bricklayers of the Information Age.

    Don't go borrowing trouble. For programmers, this means Worry only about what you need to implement.

Re: Scalar ref acting as simulatenous hash and array?
by paulbort (Hermit) on Oct 19, 2001 at 19:38 UTC
    Maybe I've completely missed the point here, but couldn't you use 'wantarray', and if it returns true, return a list, otherwise return a hash? Or is that too easy?

      Both array assignment and hash assignment cause the RHS to be evaluated in list context. wantarray can't tell the difference between array and hash assignment.

      my @arr = get_it(); my %hash = get_it(); sub get_it { if (defined wantarray) { print wantarray ? 'list' : 'scalar'; } else { print 'void'; } print " context\n"; return; }

      This prints "list context" twice.

      --
      <http://www.dave.org.uk>

      "The first rule of Perl club is you don't talk about Perl club."

      At the base of it, hashes are lists. wantarray determines between scalars and hashes-or-lists.

      ------
      We are the carpenters and bricklayers of the Information Age.

      Don't go borrowing trouble. For programmers, this means Worry only about what you need to implement.

Re: Scalar ref acting as simulatenous hash and array?
by John M. Dlugosz (Monsignor) on Oct 20, 2001 at 06:06 UTC
    From a previous discussion on functions like localtime being overloaded based on return context, I like this idea: pass in a list of which fields you want, in what order.
    my $name= $obj->('name');
    or
    my @list= $obj->(qw/name address serial zip/);
    So you don't have to remember what order they "are" in, since you specify the order you want them in. And you can pull just the ones you want.

    I suppose an actual hash could do the same thing with a slice.

    —John