6

In Python, if you index a collection structure with a out-of-bounds key/index, you get a slap in the face:

>>> [1, 2, 3][9]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError: list index out of range

That's an Exception; it derives from BaseException, and failing to handle one will crash my program, which is almost always what I want.


Perl 5 and 6's list indexing seems to not care about out-of-bounds indexing:

$ perl6
> my @l = (1..4);
[1 2 3 4]
> say @l[2];
3
> say @l[9];
(Any)
> print @l[9];                                                                                                          
Use of uninitialized value @l of type Any in string context
<snip>    
True
> my $x = @l[9]; # even assignment doesn't error! who decided this was okay!?
> say $x; print $x;
(Any)
Use of uninitialized value $x of type Any in string context
<snip>    

it's basically the same thing in Perl 5, except you don't get a value returned, but execution continues as normal.

I don't understand why out-of-bounds accessing should ever be silent. The only warnings you get that value may be "uninitialised" (but we all know it really means non-existent) are when you give it to certain functions.

Can I fix this somehow? I could implement my own post-circumfix indexing operator to override the default one that dies on junk index, but there's no way to tell the difference between an uninitialised value and the type Any. The only way to do that I can see is to check if the requested index is in the range List.elems().

What (preferably minimal, simple, clean, readable, etc) solution can I use to remedy this?


Before anyone says "yes, but the variable is uninitialised, like my $x;!": in C you get a segfault if you access memory you didn't allocate; why can't I have that sort of safety?


I've tagged this as both Perl and Perl 6 because, while I'm learning Perl 6 and the particulars of this question apply mostly to 6, the main idea seems to be a shared aspect of both 5 and 6.

dwarring
  • 4,794
  • 1
  • 26
  • 38
cat
  • 3,888
  • 5
  • 32
  • 61
  • A segmentation fault in C is better than execution continuing as if nothing happened. – cat Jan 27 '16 at 03:53
  • 2
    Perl does what you ask it to do no matter what. It assumes you know what you are asking for, well it doesn't actually assume anything. It really couldn't give a flying leap if you do know or wanted what you are asking for it will just give it to you. If you check `if defined` you don't have to use exceptions like crutches. Basically it's a paradigm shift and being a python developer this article outlines the differences of opinions fairly nicely: http://yosefk.com/blog/what-makes-cover-up-preferable-to-error-handling.html – scrappedcola Jan 27 '16 at 03:58
  • @scrappedcola thanks for the feedback! I know, you can tell I'm full of Pythonisms but I can't think of any other language where this wouldn't cause a crash, simply because there's no other logical reaction. – cat Jan 27 '16 at 04:01
  • 2
    modeling invalid elements as undefined values and having autovivification certainly makes sense and I don't see how this is not logical – perreal Jan 27 '16 at 04:10
  • @perreal but I asked for something that doesn't exist! can I at least have a message about its lack of existence or *must* I check the index exists? – cat Jan 27 '16 at 04:11
  • something that doesn't exists is logically not defined – perreal Jan 27 '16 at 04:14
  • Why *-3*? Is it a particularly bad question? Why? – cat Jan 27 '16 at 04:51
  • 2
    @cat: likely because your question is, let's say, *opinionated*; returning an undefined value for out of bounds access is a perfectly orthodox choice for a dynamic language (Perl, Ruby, Javascript, Lua, ...) – Christoph Jan 27 '16 at 05:30
  • 2
    You phrased this poorly (it reads like a rant more than a question). I think that distracts from your question about why Perl does this, which is a perfectly legitimate question. I would recommend editing out the ranty bits, you'll probably get better responses that way. – ThisSuitIsBlackNot Jan 27 '16 at 06:03
  • 2
    @cat I've edited your question to tone it down. Hopefully the downvotes will stop. – raiph Jan 27 '16 at 07:14
  • @raiph Nice editing! Thanks for making this a question that I'm pleased to upvote. – Christopher Bottoms Jan 27 '16 at 13:50
  • I would find that very annoying if I had to program in Python. – Brad Gilbert Jan 31 '16 at 23:00
  • @BradGilbert under which circumstances is autovivification ever better? – cat Jan 31 '16 at 23:03
  • @BradGilbert I'm not entirely sure what on earth that actually does having tested it but why do you want to do this with an empty list? – cat Jan 31 '16 at 23:07
  • `my @lengths; for @words { push @lengths[ .chars ], $_ }` Would you want to have to check if you already found one of at least that length, or find out the longest one before you start? – Brad Gilbert Jan 31 '16 at 23:15

4 Answers4

11

Perl 6 has shaped arrays to enforce array bounds on simple or multidimensional arrays.

From S09:

my int @ints[4;2];          # Valid indices are 0..3 ; 0..1
my @calendar[12;31;24];     # Valid indices are 0..11 ; 0..30 ; 0..23

Some more examples:

use v6;
# declare an array with four elements
my @l[4] = (1..4);
try { @l.push: 42}
say @l;
# [1 2 3 4]

These can be multi-dimensional

my @tic_tac_toe[3;3] = <x o x>, <o . o>, < x o x>;
@tic_tac_toe[1;1] = "x";      # ok
try @tic_tac_toe[3][3] = "o"; # illegal
say @tic_tac_toe;
# [[x o x] [o x o] [x o x]]
dwarring
  • 4,794
  • 1
  • 26
  • 38
10

This will do what you want.

my @a is default(Failure.new('IndexError'));
@a[0] = 1;
say @a[1];
say 'alive';

output:

===SORRY!===
IndexError

If you want a stacktrace you either have to run perl6 --ll-exception or create your own container with Proxy. (This is a bug, reported as: RT#127414)

see: https://doc.perl6.org/type/Failure
see: https://doc.perl6.org/routine/is%20default
see: https://perl6advent.wordpress.com/2015/12/02/day-2-2-bind-or-2-bind/

Oh, and please don't listen to the naysayers who want to tell you to adapt to Perl 6. The whole point of Perl 6 is that you can mend it and bend it to your will.

class NonvivArray is Array {
    multi method AT-POS(NonvivArray:D: Int:D $pos) is raw {
        say 'foo';
        my $val = callsame;
        X::OutOfRange.new(got=>$pos, range=>0..self.elems-1).throw unless $val;
        $val;
    }

    multi method AT-POS(NonvivArray:D: int $ipos) is raw {
        say 'foo';
        my $val = callsame;
        X::OutOfRange.new(got=>$ipos, range=>0..self.elems-1).throw unless $val;
        $val;
    }
}

my NonvivArray $a;
$a.push: 1;
dd $a;
say $a[1];

There is more then two ways to do it. Here is the third.

try { 
    my Int:D @a; 
    say @a[0]; 
    CATCH { default { say .^name } } 
}
# OUTPUT«X::Method::NotFound␤»

One can argue that the exception thrown is LTA or that this should actually be special cased. However, it does what the OP asked for without fiddling with implementation details of Array as implemented by Rakudo.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • 1
    Cool! this is more what I was looking for! Now, can I, for instance, define a type that inherits from `List` and which defaults to `Failure.new("IndexError")` so I just have to write `my SafeList @a;` instead? – cat Jan 28 '16 at 21:57
  • 3
    I added a subclass to Array. I do not claim to know what I am doing. This will most likely blow in your face as soon as you use infinite lists and/or promises (that are infinite lists until they are not). That you can bend Perl 6 to your will does not mean it wont bite you. –  Jan 28 '16 at 22:50
  • 3
    I have read and hereby accept all terms of the Unpredictable Software User License Agreement – cat Jan 28 '16 at 23:02
  • 1
    The lack of stacktrace is actually a bug. It's even in the instance of Failure, just not displayed. –  Jan 29 '16 at 13:46
  • The NonvivArray won't allow you to hold any undefined values. It'd be better if it checked for EXISTS-POS instead of working just with the return value of AT-POS. – timotimo Feb 01 '16 at 02:58
5

[Since you asked for both a Perl5 and a Perl6 answer, but you only got a Perl6 answer, here's a Perl5 answer.]

You could write an op checker that replaces the indexing ops with ones that check bounds (in the same way that no autovivification; replaces dereferencing ops with versions that don't autovivify.)

ikegami
  • 367,544
  • 15
  • 269
  • 518
2

You can get inspired by Acme::Array::MaxSize - i.e. you can use Tie::Array to intercept the array operations.

choroba
  • 231,213
  • 25
  • 204
  • 289