2

I have been tinkering with the idea of hiding some implementation details of an IPC mechanism behind a tied array. The goal is to be able to do something like this on the server side:

# setup code here. Client provides a function name, we find
# a function to deal with the request:
my $coderef = lookup($method);
local @_;
tie @_, 'My::IPC';
@ret = &$coderef;

The My::IPC class will then read serialized objects from a pipe/socket as needed (triggered through the SHIFT or FETCH method).

I would like to provide the authors of the server functions with ways to write his IPC-accessible functions in a way that he would write local functions, i.e.:

sub f1 {
    while (my $param = shift) {
        ...
    }
}

... as well as ...

sub f2 {
    my ($foo, $bar, $baz, %flags) = @_;
    ...
}

f1 is intended to be able to deal with streams of data that may be larger than the amount of RAM available -- as long as each individual object fits into RAM after deserialization, everything is fine. f2 is intended for "simpler" functions where the argument list can be slurped into RAM.

To support both scenarios, the TIEARRAY constuctor and the SHIFT, FETCH, and FETCHSIZE methods need to be implemented. I consider that part solved. What bothers me is that I can't seem to find a way that lets me transmit undef values to f1 because even in list context, shift returns undef when used on an empty array. Something like

@params = splice @_, 0, 1;

might work here, but that does not exactly look like the obvious solution to users.

I could solve this by making a slight modification to f1 and by implementing FETCHSIZE in a way that it returns 1 as long as there is data available:

sub f3 {
    while (@_) {
        my $param = shift;
        ...
    }
}

But this would break f2 because only the first element would get assigned. Apparently, FETCHSIZE needs to provide an accurate value, but to get that accurate value, the entire array needs to be slurped into RAM -- which defeats the purpose of iterating over it.

Is there an elegant way to support both a "streaming" model (f1, f3) and a more simple function-call-like model (f2) with the same tied array implementation?

hillu
  • 9,423
  • 4
  • 26
  • 30

1 Answers1

5

Arrays have a length. If your code can't behave like an array, giving it the interface of an array isn't a good idea.

You can get the underlying object using tied

sub f1 {
   while ( my ($param) = tied(@_)->next() ) {
      ...
   }
}

sub f2 {
   my ($foo, $bar, $baz, %flags) = @_;
   ...
}

But avoiding tying something that isn't array-like is probably best.

sub f1 {
   my $o = shift;
   while ( my ($param) = $o->next ) {
      ...
   }
}

sub f2 {
   my ($foo, $bar, $baz, %flags) = shift->all;
   ...
}

Note that next is called in list context, so it has the option of returning an empty list in addition to returning undef, allowing the end of the iteration to be distinguishable from undef.

ikegami
  • 367,544
  • 15
  • 269
  • 518