Raku / problem-solving

🦋 Problem Solving, a repo for handling problems that require review, deliberation and possibly debate
Artistic License 2.0
70 stars 16 forks source link

Make [Z] and friends DWIM #443

Open lizmat opened 1 month ago

lizmat commented 1 month ago

Motivation

To make reduce DWIM when used with a +-slurpy operator on a list-of-lists.

That is, to make operations like the following work without bad surprises:

my \transpose = [Z] matrix;    # (\matrix and \transpose are each a list-of-lists)

my \sum-vector = [Z+] vectors  # (\vectors is a list-of-lists, \sum-vector is a list)

my \divisors-of-n = [X*] prime-factors-of-n.map(&powers-below-n-of);

In fact:

(This all applies to both the subroutine form of reduce, and to the [ ] reduction meta-operator. In this RFC, reduce always refers to both.)

How it currently breaks

The edge-case of being passed a list-of-list with .elems == 1 breaks DWIM expectations:

my @v;
@v := (<100 200>, <10 20>, <1 2>,); say [Z+] @v;  # (111 222) -- All good.
@v := (<100 200>, <10 20>,);        say [Z+] @v;  # (110 220) -- All good.
@v := (<100 200>,);                 say [Z+] @v;  # (300)     -- WAT, expected (100 200)

Why it currently breaks

A double application of the single-argument rule:

  1. Once for passing list-of-lists to reduce, removing the outer list.
  2. Once more, if list-of-lists has exactly one child list, when reduce calls its operator with that child list as the only argument (and the operator has itself a +-slurpy signature).

Prior discussion

Change proposal

  1. Make reduce check the operator it's given, and if it's a Callable with a single-argument slurpy, then in the one-element case where the operator needs to be called with a single argument, call it as op (element,).
  2. Make sure that all built-ins like Z, X, zip, roundrobin which follow the single-argument-rule, actually report a + slurpy signature when introspected.1

Why change 1 needn't be considered a dirty 'special case'

On a technical level, the proposed change no.1 would probably be implemented with additional if/else branching in the implementation of reduce, based on introspection of the operator that was passed to it — and adding special cases to built-in routines is usually undesirable.

However, this particular branching is not arbitrary and can be seen as part of a consistent rule.

In this view, a +-slurpy in a routine signature is simply a kind of "calling convention" that specifies how you need to pass arguments to the routine:

Now, when reduce calls the operator it was given, it too has specific reasons to want a certain number of arguments to arrive at the operator:

So arguably, whatever calling convention the operator given to reduce uses, reduce should, in the single-element case, call the operator in a way that means "I want to pass this single element to you". For a +-slurpy operator, that happens to be op (element,);

Furthermore, reduce already introspects its operator in various ways (associativity, arity, etc.) in order to DWIM as much as possible, so this would probably fit right in.

Risks

Anti-risks


1) Maybe they all already do, but I know that in the past some showed up as code>**@</code or similar and did the single-argument-rule handling manually inside the routine, so an audit would be needed to make sure no such cases still remain in the setting.

lizmat commented 1 month ago

Originally suggested by @smls in https://github.com/rakudo/rakudo/issues/2025

Leont commented 1 month ago

I have been bitten by this before, the current behavior always felt like a footgun to me