Skip to content

Instantly share code, notes, and snippets.

@Xliff
Last active June 15, 2023 03:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Xliff/c6e5b7fb9fe5ea1148604b9d2c7312ee to your computer and use it in GitHub Desktop.
Save Xliff/c6e5b7fb9fe5ea1148604b9d2c7312ee to your computer and use it in GitHub Desktop.
BQN in Raku - Why not? Here's the range operator: ↕

I was just introduced to BQN, recently. While it won't be my first choice of language until I understand it a lot more, it does provide an interesting insight into all things Listy.

So I thought I might grab some low hanging fruit and bring some of that BQN goodness into Raku, a language that I -am- using on the daily.

Here's an implementation for the Range operator (↕):

multi sub prefix:<>(List() $l) { 
  my @a = [X]( |$l.map({ 0..$_ }) ); 
  +@a == 1 ?? @a.head !! @a 
};

We're doing a bit of optimization on that last line that will become aparent in our next example:

say ↕5 # (0 1 2 3 4)

Without that last line, it would have been ((0 1 2 3 4)) which would have not been orthogonal to the rest of the results.

Here's a better example:

say ↕(1,2) # [(0 0) (0 1) (0 2) (1 0) (1 1) (1 2)]

And this should work for an arbitrary list size, so

.say for ↕(1, 2, 2, 5)

Will do what you expect and print out the following:

(0 0 0 0)
(0 0 0 1)
(0 0 0 2)
(0 0 0 3)
(0 0 0 4)
(0 0 0 5)
(0 0 1 0)
(0 0 1 1)
(0 0 1 2)
(0 0 1 3)
(0 0 1 4)
(0 0 1 5)
(0 0 2 0)
(0 0 2 1)
(0 0 2 2)
(0 0 2 3)
(0 0 2 4)
(0 0 2 5)
(0 1 0 0)
(0 1 0 1)
(0 1 0 2)
(0 1 0 3)
(0 1 0 4)
(0 1 0 5)
(0 1 1 0)
(0 1 1 1)
(0 1 1 2)
(0 1 1 3)
(0 1 1 4)
(0 1 1 5)
(0 1 2 0)
(0 1 2 1)
(0 1 2 2)
(0 1 2 3)
(0 1 2 4)
(0 1 2 5)
(0 2 0 0)
(0 2 0 1)
(0 2 0 2)
(0 2 0 3)
(0 2 0 4)
(0 2 0 5)
(0 2 1 0)
(0 2 1 1)
(0 2 1 2)
(0 2 1 3)
(0 2 1 4)
(0 2 1 5)
(0 2 2 0)
(0 2 2 1)
(0 2 2 2)
(0 2 2 3)
(0 2 2 4)
(0 2 2 5)
(1 0 0 0)
(1 0 0 1)
(1 0 0 2)
(1 0 0 3)
(1 0 0 4)
(1 0 0 5)
(1 0 1 0)
(1 0 1 1)
(1 0 1 2)
(1 0 1 3)
(1 0 1 4)
(1 0 1 5)
(1 0 2 0)
(1 0 2 1)
(1 0 2 2)
(1 0 2 3)
(1 0 2 4)
(1 0 2 5)
(1 1 0 0)
(1 1 0 1)
(1 1 0 2)
(1 1 0 3)
(1 1 0 4)
(1 1 0 5)
(1 1 1 0)
(1 1 1 1)
(1 1 1 2)
(1 1 1 3)
(1 1 1 4)
(1 1 1 5)
(1 1 2 0)
(1 1 2 1)
(1 1 2 2)
(1 1 2 3)
(1 1 2 4)
(1 1 2 5)
(1 2 0 0)
(1 2 0 1)
(1 2 0 2)
(1 2 0 3)
(1 2 0 4)
(1 2 0 5)
(1 2 1 0)
(1 2 1 1)
(1 2 1 2)
(1 2 1 3)
(1 2 1 4)
(1 2 1 5)
(1 2 2 0)
(1 2 2 1)
(1 2 2 2)
(1 2 2 3)
(1 2 2 4)
(1 2 2 5)

If anyone would be interested in more of these, please let me know!

@raiph
Copy link

raiph commented Jun 12, 2023

say ↕5 # (0 1 2 3 4) is wrong. It displays (0 1 2 3 4 5), as I would expect.

@0racle
Copy link

0racle commented Jun 14, 2023

I think I would just declare a multi (rather than branch in the sub) since dispatch is pretty efficient

multi sub prefix:<>(Int $n) { (^$n).List }
multi sub prefix:<>(*@ns) { [X] @ns.map(^*) }

I'm not sure if it's possible to get the precedence working such that ↕2‿3‿4 would work, but for now parens are needed, as ↕(2‿3‿4)

As mentioned in my Reddit comment, this function is not exactly BQN's Range, as it does not shape the result. This is more akin to K's Odometer.

@0racle
Copy link

0racle commented Jun 15, 2023

Re: shaping the output... if you define shape as follows, and then pass each call of (bqn-range) through shape, you don't need a multi (but it does mean your passing a flat list though batch unnecessarily.

sub shape($a is copy, *@d) {
    while @d.pop -> $d { $a .= batch($d) }
    return |$a.head
}

sub bqn-range(*@n) { shape(([X] @n.map(^*)), @n) }

say bqn-range(10);
# (0 1 2 3 4 5 6 7 8 9)

.say for bqn-range(3,3);
# ((0 0) (0 1) (0 2))
# ((1 0) (1 1) (1 2))
# ((2 0) (2 1) (2 2))

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment