Home > Raku > Not even empty

Not even empty

Another weekend, another rabbit hole. While reading Arne’s solution for challenge #213, I thought to myself: “If the task can be written in one sentence, why does the answer takes more then one line?”. A reasonable question, given the answer is written in Raku. After some wiggling, I found a shorter version.

my @list = <1 2 3 4 5 6>;
@list.classify({ .Int %% 2 ?? 'even' !! 'odd'}){'even','odd'}».sort.put;

To use .classify, as Arne did further down, was the key, because postcircumfix:<{ }> takes more then one key. The resulting lists are hyper-.sorted and .put will not only show more then 100 elements (please don’t use say unless you actually want that) but will also flatten.

Futher on in his post, he tackled some error handling. I wanted that too and to my delight X::Str::Numeric stores the inconvertible thing inside .source.

CATCH {
    when X::Str::Numeric { put „Sorry, I don't know how to handle "{.source}" in my input list.“ }
}

But how would I deal with undefined values? This is not far-fetched. If you get some CSVs that popped out of a less-then-optimal Excel sheet or don’t take the possibility of SQL null into account, you can easily end up with a type-object where you don’t want one. For scalars we can guard code with type-smilies.

sub foo(@a) { say @a.elems };
foo(List); # 1

For me an Array or a List are a place where a (semi-infinitely long and very narrow) box, that could contain stuff, could have been, but isn’t. Rakudo doesn’t care and just binds the undefined value to the @-sigiled symbol. As usual, I was enlightened on IRC. Any.list will try its best to turn a single element into a List with a single element. For 42 that makes perfect sense and allows us to worry less in day-by-day code. For type-objects that leads the question: “How many things are stored in you?”, to be answered with nonsense. I wouldn’t be surprised to learn that this leads to hard to track down bugs. My .t-files typically don’t sport copious testing against undefined values, because I falsely believed that :D would safe my bum.

sub bar(@a where .[0].defined) { say @a.elems };
bar(List);

This is ugly, imprecise and doesn’t really do what I want. When the where-clause is triggered the binding has already happened. I have the feeling that the right solution is to let Any.list fail when called on a type object. As lizmat pointed out, that is a breaking change. It may look benign but there is tons of fault-tolerant-by-accident code out there that relies on sloppy handling of undefined values. In the IRC discussion, lizmat stated she didn’t like the my @a := Int; case. I’m actually fine with that, because the intent of the Raku-programmer (and we want more of those, don’t we?) is clear. The silent case when @-sigiled symbols (not @-sigiled containers!) in Signatures are bound to type-objects worries me. It is certainly possible to change that but may have performance implications. It too could be a breaking change and it is hard to tell how many lurking bugs we would squash with a fix.

Yet, I would really like Raku to be WAT-free, because I surely want Raku to be more sticky.

UPDATE:

The following can be used to make .elems fail early. That prefix:<+> doesn’t go through that method leads to +@a return 0 while .elems is 1. Also, Parameter.modifier is an ENODOC.

    Any.^can('elems')[0].candidates.grep({ .signature.params[0].&{ .type, .modifier } ~~ (Any, ':U') }).head\
        .wrap(my method elems { die(‘Abstract containters don't know how to count’) });
Categories: Raku
  1. habereetdispertire
    April 23, 2023 at 22:07

    > If the task can be written in one sentence, why does the answer takes more then one line?

    That’s a lovely thought. It feels like `raku` comes closer to thought in that it generally seems to have a way to match that brevity without compromising too much on readability. I completed the raku track on Exercism and half my solutions are under ten lines! My method chain attempt at #213 was similar to your solution:

    ~~~raku
    sub fun-sort( *@list ) {

    @list
    .classify: * %% 2 andthen
    .{ True, False }
    .grep: *.defined andthen
    .map: *.sort.Slip

    }
    ~~~

  1. April 24, 2023 at 19:39

Leave a comment