* Mark Fowler <[EMAIL PROTECTED]> [2002-07-18 07:40]:
> Okay, I had a look at this as promised and ran into some problems.  After 
> writing two pages of tests (gee, that was fun) I realised that this was a 
> lot more complicated that we'd originally planned.
> 
> First, up, first and last
> -------------------------
> 
> [% mylist.first(-2) %]
> 
> I think that should return an empty list.  It's not very perlish, but, 
> well, this isn't Perl.  Comments?

Since first and last would be defined as:

    'first'   => sub {
        my $list = shift;
        return $list->[0] unless @_;
        return list_splice_vmeth($list, 0, $_[0]);
    }
    'last'    => sub {
        my $list = shift;
        return $list->[$#$list] unless @_;
        return list_splice_vmeth($list, -$_[0], $_[0]);
    }

(with list_splice_vmeth defined as:

sub list_splice_vmeth {
    my ($list, $offset, $length, @replace) = @_;
    my @newlist = @$list;

    if (@replace) {
        splice @newlist, $offset, $length, @replace;
        return \@newlist;
    }

    return [ splice(@newlist, $offset, $length) ]
        if (defined $length);

    return [ splice(@newlist, $offset) ]
        if (defined $offset);

    return [ splice(@newlist) ];
}

Notice that it feeds everything to CORE::splice, this will be important
later.)

This will return (some example data, too):

  [% mylist = [ 1 2 3 4 5 6 7 8 9 0 ];
    "'$l' " FOREACH l = mylist.first(-2) %]
  '1' '2' '3' '4' '5' '6' '7' '8' 

Why would we make it work differently from Perl?

If we decide they should work differently, then I'd suggest that they
return the first element of the list (the method is called "first",
after all):

    'first'   => sub {
        my $list = shift;
        return $list->[0] unless @_ && int $_[0] > 0;
        return list_splice_vmeth($list, 0, $_[0]);
    }

  [% mylist = [ 1 2 3 4 5 6 7 8 9 0 ];
    "'$l' " FOREACH l = mylist.first(-2) %]
  '1'

(The examples below were tested with both the "&& int..." line above and
without, and return identical results, except for the example
immediately above.)

> What happens if I say .first(10) and there's only three items in my list?  
> I'm inclined to say we return a list with three items in it and no undefs.  

/me nods.

> This makes much more sense when you consider you're quite likely to want
> to do things like:
> 
>   [% FOREACH bob = mylist.first(10) %]
>    [% IF loop.first %]My top ten items are: <ul>[% END %]
>    <li>[% item %]</li>
>    [% IF loop.last %]</ul>[% END %]
>   [% END %]

  [% mylist = [ 1 2 3 ];
    "'$l' " FOREACH l = mylist.first(10) %]
  '1' '2' '3'

> And following on from that first and last when applied to scalars always
> return a list containing just the scalar, no matter what the arguments
> were.

list ops applied to scalars end up with the scalars being promoted to 1
element lists, and then the vmeth is applied.  Is that what you mean?
or a separate set of $SCALAR_OPS entries:

  'first' => sub { $_[0] },
  'last'  => sub { $_[0] },

> Slice
> -----
> 
> Right, I think that slice should return a copy of the list, and return the 
> bits mentioned.
> 
> [% newlist = mylist.slice(2,3) %] 
>   -- returns 3rd, 4th, and 5th as new list

Yep (see the list_splice_vmeth, above):

  [% mylist = [ 1 2 3 4 5 6 7 8 9 0 ];
    newlist = mylist.slice(2,3);
    "'$l' " FOREACH l = newlist %]
  '3' '4' '5' 

> [% newlist = mylist.slice(-3,2) %]
>   -- returns a new list containing the third to last and penultimate items

  [% mylist = [ 1 2 3 4 5 6 7 8 9 0 ];
    newlist = mylist.slice(-3, 2); 
    "'$l' " FOREACH l = newlist %]
  '8' '9'

> [% newlist = mylist.slice(-3,4) %]
>   -- returns a new list containing the last three items, and nothing
>      more, just like first and last did.

  [% mylist = [ 1 2 3 4 5 6 7 8 9 0 ];
    newlist = mylist.slice(-3,4);
    "'$l' " FOREACH l = newlist %]
  '8' '9' '0' 

> In this way, first and last can be implemented in terms of slice.
> 
> I'm not sure if this should do the right thing or not:
> 
> [% newlist = mylist.slice(-1,-3) %]
>
> and return a list containing the last, the penultimate, and the element
> before that in that order.  Essentially it's the same as 
> 
> [% mylist.last(3).reverse %]
> 
> Is this feature bloat or logically doing the right thing.  Tricky.

Yes. :)

I'd be most inclined to not try any cleverness in the vmeths, and keep
the implementation as close as possibly to Perl's splice behaviors.  At
least that way, it's not a surprise to everyone, just the people who
don't already know Perl (as opposed to other ways, which everyone would
have to relearn.)

> Split
> -----

Splice?

> Oooh, how complicated is this.  The currently suggested implementation of
> split works by shallow copying the array, deleting/replacing elements,
> throwing that array away and returning the old elements you've
> deleted/replaced.
>
> I don't like this implementation.  For a start, it makes it impossible to 
> insert elements in the middle of array, which is pretty much what I want 
> to use split before.

I struggled with this for a while myself, and figured that, since most
vmeths (with the exceptions of push, pop, shift, and unshift) are
non-destructive, it made more sense to continue with this trend.  The
only time this is an issue is when there are replacements specified:

  [% mylist.splice(0, 1, "hello world") %]

And in my implementation above, I'm explicitly returning a copied array:

    if (@replace) {
        splice @newlist, $offset, $length, @replace;
        return \@newlist;
    }

    which could be changed to:

    if (@replace) {
        splice @$list, $offset, $length, @replace;
        return $list;
    }

And allow for inplace modification.  Or you could just do:

    [% mylist = mylist.splice(0, 1, "hello world") %]

Since the other versions of splice are non-destructive, making this
version an exception rubs me the wrong way.  However, it also goes
against my earlier statement of "we should do it the way Perl does it",
because CORE::splice modifies the list.

> The other options are
> 
>  a) Copying the array, then returning the updated array after you've
>     deleted/replaced replaced operations.  This is okay, but it's a little 
>     expensive.  This will allow you to write constructs like
> 
>     [% FOREACH item = mylist.splice(10,0,newlist) %]
> 
>     and 
> 
>     [% mylist = mylist.splice(10,0,newlist) %]    
> 
>     but won't let you alter big lists inplace, meaning that every splice
>     involves copying the entire array.

Yeah, but this is Perl; you probably shouldn't be worried about memory.
We're creating and deleting AV's all over the place. :)

>  b) Edit the list inplace and return what you've just spliced out.  This
>     is pretty much what perl does with it's list splicing operation
>     and gives us the most similar syntax to slice too.  It does leave
>     us with some silly syntax though
> 
>     [% templist = mylist;
>        dummy = templist.splice(10,0,newlist);   
>        FOREACH item = templist %]
> 
>     note we need the dummy to stop spitting the replacement list out to
>     the template we're creating.  

Or: 

  [% CALL templist.splice(10, 0, newlist) %]

But why wouldn't you do:

  [% FOREACH item = mylist.splice(10, 0, newlist) %]

And not have to modify any of the lists at all?

>  c) Edit the list inplace and return nothing.
> 
>     This means this will now work how I'd expect it to:
> 
>     [% foo.splice(10,2,3) %]   # remove 3rd, 4th, 5th elements from foo
> 
>     Without spitting things to the output or creating a temporary
>     variable.

Again,

  [% CALL foo.splice(10, 2, 3) %]

Or the more general:

    if (@replace) {
        splice @$list, $offset, $length, @replace;
        return "";
    }

> Which one of these we choose is a matter of debate.  I'm inclined to
> think that the last is the best trade off between speed and
> flexibility and ease of use...but you're more than welcome to
> disagree.
> 
> Comments really gratefully received.
> 
> Oh finally a note on some syntax that we can't support with splice and
> still do the right thing.  In order for this to work correctly
> (auto-promotion of a scalar to a list)
> 
>   splice(10,0,ascalar)
> 
> This can't work
> 
>   splice(10,0,ascalar,asecondscalar,athirdscalar)
> 
> As we can't tell then if this
> 
>   splice(10,0,alistcontainingjustonescalar)
> 
> means insert that scalar at the 11th place in the list, or insert a
> listref containing that scalar at the 11th place in the list.
> 
> I propose we throw an error if someone writes more than a three
> argument version of splice.

ok...

  sub list_splice_vmeth {
      my ($list, $offset, $length, $replace) = @_;
      my @newlist = @$list;

      if (@_) {
          return Template::Exception->new("vmeth",
                  "Too many arguments to splice");
      }

      if ($replace) {
          splice @newlist, $offset, $length, $replace;
          return "";
      }

      return [ splice(@newlist, $offset, $length) ]
          if (defined $length);
      
      return [ splice(@newlist, $offset) ]
          if (defined $offset);
      
      return [ splice(@newlist) ];
  }


> Language design is hard.

... and now I'm confused.  But, like I said before, I'm opposed to
having too much magic in the vmeths.

(darren)

-- 
The smart way to keep people passive and obedient is to strictly limit
the spectrum of acceptable opinion, but allow very lively debate
within that spectrum.
    -- Noam Chomsky


Reply via email to