(∾´∾⟜"Fizz"‿"Buzz"/˜((¬∨´)⊸∾0=3‿5|⊢))¨1+↕
Yes, this is what we are going to recreate, step by step - wait, wait don’t run away quite yet, I hope I can show you that this is much more than just a silly little code-golf exercise (not that there is anything wrong with that)!
This is going to be a wee bit longer, since we are going to try to learn the basics of BQN by implementing a well-known problem, FizzBuzz. And BQN is different, very different (well, if you are not familiar with array programming that is), which makes it such an exciting topic to learn!
BQN is an array programming language, where array programming is a paradigm, like functional programming or OOP is a paradigm.
It also describes itself as an APL for your flying saucer - soo, what is APL then? It is pretty much the OG array programming language, invented by Ken Iverson in the 60s and BQN places itself firmly in the tradition and family of Iversionian languages (like APL, J and K).
They all have certain attributes in common, like a very terse syntax, usually with mysterious symbols and the execution direction is from right to left, showing their close relation to math.
Alright, this is probably still a bit dry, I think a few exercises might illustrate those properties better, and why they can be so intellectually stimulating.
You probably want to hack along, an excellent and super easy way to do that is by using BQNPAD - nothing to setup, just visit the website and write away.
To start things off, how about we sum up all numbers in an array?
In most languages you would have something like a for
loop to do the job, if it is a functional language or has functional elements, you might have something like a fold
function instead. BQN is closer to functional languages (in a lot of regards) and also provides a fold
primitive.
But first, what would an array in BQN look like?
In a lot of languages it would be something like this: [1, 2, 3]
.
In BQN it looks like this: 1‿2‿3
.
We will not go into the thought process behind this and why it differs from the arguably cleaner looking syntax in APL, but there is a detailed write-up about it in the BQN docs - in fact the whole BQN project is extremely well-documented, not only with dry syntax explanation but often also with behind-the-scenes insights. Well worth a read!
Now, sum/reduce is implemented with the sum
function and modifying it with fold
. In BQN it looks like this:
+´
And the whole program doing the sum/reduce on the array could look something like this:
+´ 1‿2‿3
I think this is very elegant and clear!
You might not think so quite yet, I know this is an aquired taste, so let me explain my joy when seeing this:
While most array programming languages have a relatively small amount of primitives, those can often be combined to form more complex constructs without having to introduce new specialized constructs. This is sometimes referred to as orthogonal design in languages. This creates a sense of discoverability, that can lead to thought processes like this: “So, can I combine this? I have seen something similar before just with another function- oh wow that works, amazing! And now I add this thing here and- what?! That works too?”.
Here is a multiply/reduce for example:
×´ 1‿2‿3
This puzzle/discover game is one of the things that makes array programming so addictive - I hope I can manage to nudge you enough in the right direction so that you can also enjoy this part!
(∾´∾⟜"Fizz"‿"Buzz"/˜((¬∨´)⊸∾0=3‿5|⊢))¨1+↕100
This is from rosettacode.org - I chose this one instead of my own solution, since it has a better mix of language primitives to demonstrate a few of the cool things of BQN and array programming languages in general.
Because, yes, we are not really doing this to save the planet with the power of FizzBuzz, but to give our poor, sore, tik-tok overloaded brains a nice deep massage!
↕
This beauty is the range function and will create an array with n elements, where n is the number you provide- this sounds way more complicated than it is, here, have a few examples instead:
↕3
⟨0 1 2⟩
↕5
⟨0 1 2 3 4⟩
Wait, you are coding along, right? It’s fun, do it, enter a few of those yourself! In BQNPAD you would get this symbol by typing \d
.
I have to admit, I kinda lied to you. When I said ↕
is the range function… well that is not necessarily true. It can be the range function but only when it is used as a monadic function. Huh, did everyone just leave? Oh no there is one person left- oh, you are an haskell programmer? Ah right.
Monadic in array programming languages is nothing complicated, it just means that the function takes one argument. ↕42
- 42 is the single argument of the range function. But it can have a different meaning when the function takes two arguments, this is called dyadic:
3↕0‿1‿2‿3
0 1 2
1 2 3
This is now the window function, it creates overlapping slices, of length 3 in this example.
So, to summarize: ↕
has two meanings, monadically it is the range function, dyadically it is the window function.
If you have a keen eye, you might’ve spotted another difference to most other programming languages: The functions are infix, meaning, you have one argument on the right and another (optional) argument on the left of the function symbol.
In our FizzBuzz program the ↕
function is monadic, on the right is another function which does not act as a second argument to ↕
. We will later get to functions that can take modifiers as arguments, which kinda look like functions themselves, but later, this is already a lot of new stuff.
You could write the window example above also like this:
3↕↕4
The reason I am showing this is to drive home aother important, and unusual concept, the execution direction from right to left.
Let’s read this together: We have the number 4, which acts as the argument to the range function to its left. It is the range function because to its left is another ↕
, which is not a valid second argument to ↕
. If it were a number or array then the function would be the window function. Anyways, next, we have another ↕
, it takes as its first argument the result of ↕4
. And it has a 3 to its left, which acts as its second argument. That makes it the window function. Or written in pseudo code:
window(range(4), 3)
So, aside from showing the range and window functions, we talked about a few important concepts that are common in a lot of array programming languages:
+
Alright, so we have an array of numbers, but there is one little problem, it starts with 0. For FizzBuzz we want to have the numbers start with 1 instead.
So, we add one to each number in the array that we previously produced with the range function:
1+↕100
This is an attribute that a lot of array languages share, called Rank Polymorphism - which, simplified, means that the primitives work on different rank arrays. Rank I say? For now, think of it as dimensions, again, simplified, so you can use the +
primitive on vectors together with matrices, or any other combination, even scalars like in our case.
There are examples of similar functionality implemented in other languages:
Broadcasting in julia
1 .+ collect(range(1, 10))
Broadcasting in Numpy
import numpy as np
np.array([1]) + np.arange(0, 10)
¨
Written /2
.
Each is somewhat of a new thing for us: It is not a simple function but a modifier. It will modify the function to its left, deriving a new function that will get each element to the right.
≠
Written \/
.
It is probably easier to demonstrate each
by showing it with another primitive: length
.
It returns the length of a list:
≠ 12‿13‿14
3
But what if we want to get the length of each list in a list of lists (nested list)?
Just using length by itself again will only return the length of the enclosing list:
≠ ⟨⟨12,13,14⟩,⟨1,2,3,4⟩⟩
2
Each will help us here, it will yield each
sublist and apply the function it modified on it:
≠¨ ⟨⟨12,13,14⟩,⟨1,2,3,4⟩⟩
⟨3 4⟩
Ok, back to our FizzBuzz program: each will yield every element of the list of numbers from 1 to 100 to the function to its left, which in our case is everything in the paranthesis:
(everything here will act as the function for each)¨1+↕100
Now, let’s dig into that part!
⊢
Written \}
.
This one might seem a bit useless at first, but the identity functions will become especially useful when you write tacit code. But let’s not overload ourselves, here it just means: “Replace with right argument”.
In our program it means that the right identity will be replaced with each of the numbers from 1 to 100. It will iterate over that range and yield the value to the rest of the function:
(...⊢)¨↕100
|
The range of 1 to 100 will be used as the argument of the next function, which is modulus
, so, it will return the remainder of the division of the right argument by the left argument. It is a dyadic function, its left argument is 3‿5
.
When it is not a single number then it will check the remainders for all elements of the array:
3‿5|12
⟨0 2⟩
This is definitely part of the “to think in arrays” skillset that one will learn after doing array programming for a while.
(...3‿5|⊢)¨1+↕5
⟨ ⟨ 1 1 ⟩ ⟨ 2 2 ⟩ ⟨ 0 3 ⟩ ⟨ 1 4 ⟩ ⟨ 2 0 ⟩ ⟩
Divide each right argument by 3 and by 5, resulting in a list of lists (the result for each 3‿5 division).
The first ⟨1 1⟩
is the result of 3‿5|1
, ⟨2 2⟩
the result of 3‿5|2
, ⟨0 3⟩
the result of 3‿5|3
etc.
=
equal
is pretty straight forward, it will check its right argument for any occurences of the left argument:
0=3‿5|3
⟨1 0⟩
∾
Written \,
join
joins two lists together.
For example:
12‿13‿14∾1‿2‿3
⟨12 13 14 1 2 3⟩
"abc"∾"def"
"abcdef"
It will join together our list of lists (with their two elements signifying if the number was dividible by 3 or 5) with something else.
What exactly we join together will become clear once we have worked through the next part:
(¬∨´)⊸∾
⊸
Written \h
before
is quite the interesting little primitive: It is a combinator. Combinators can be used to add/change excution structure. BQN has quite a few of those, which is really cool! But it will probably take a little bit until their usage becomes second nature (and can be properly appreciated).
Anyways, given you have two functions, 𝔽
and 𝔾
and one argument 𝕩
:
𝔽⊸𝔾 𝕩
The before
combinator shuffles around a few things:
(𝔽 𝕩) 𝔾 𝕩
Here is a visual helper: The pointy part of before points to the first function to be executed, while the round part points to the finalizing function that returns the result of this expression.
Getting back to our FizzBuzz problem:
The 𝔾
in our case is the join ∾
function, while 𝔽
is (¬∨´)
.
If we take a simple value for 𝕩
, say 12, we will have the following scenario:
0=3‿5|12
⟨1 0⟩
It might the help to replace the before combinator with the expanded form:
((¬∨´) 1‿0)∾1‿0
This is the written out form of (𝔽𝕩) 𝔾𝕩
, applied to our example.
Alright, let’s try to make sense of this part then: ¬∨´
.
´
Written \5
fold
is a classic, especially in functional programming. It is a modifier and will need a function to fold over with.
We saw an example of fold a bit earlier, when we were looking at sum/reduce. Here, we are doing an or/reduce ∨´
:
∨´ 1‿0
1
∨
Written \v
The or
we used with our fold is a logical or, so as long as there is one 1
it will fold to 1
:
∨´ 1‿0‿0‿0
1
∨´ 0‿0
0
¬
Written \~
not
is is a logical not and will negate our numbers. We currently have the ∨´ 1‿0
which is 1
and negated to a 0
.
We then join it to the 1‿0
(the result of 0=3‿5|12
) which will result in ⟨0 1 0⟩
.
˜
Written \
`
swap
is a modifier to a function (to its left). We have seen a few modifiers already, like before and fold. swap
swaps the left and right arguments of its function to the left that it is modifying:
"foo"∾˜"moo"
"moofoo"
"foo"∾"moo"
"foomoo"
Swap is often used to fix which argument is the left and which is the right for a function.
/
In our FizzBuzz program we are swapping the arguments for the replicate
function.
So, what is the replicate
function you ask? Let’s have a look how it works:
1‿0‿0‿1/"abcd"
"ad"
Here, we see something in action, that is quite common in array programming languages: A boolean mask. We have seen something similar already when we compared the results of our Modulus to zero (0=3‿5|⊢
) - that generated a boolean mask that we could then use for other operations - this is very common and a different way of thinking about how to write programs.
We are using Replicate
on "Fizz"‿"Buzz"
to the left (that’s why we swap the arguments around).
But maybe you remember that the other argument, the one that will serve as the boolean mask for replicate, is a list with 3-elements. “Fizz”‿“Buzz” is a list with only two elements, so how would that work?
0‿0‿1/"Fizz"‿"Buzz"
Turns out, that doesn’t actually work, also, if you are familiar with the FizzBuzz problem, we don’t seem to address the case where a number is divisible by 5 and by 3, which would translate into “FizzBuzz”.
The rest of the program solves that issue, but to understand it, we will need to learn one more primitive.
⟜
Written /l
Bind/After is similar and related to before
which we looked at already. Here, we are using it to modify join
:
∾⟜ "Fizz"‿Buzz" 𝕩
which expands to 𝕩 ∾ "Fizz"‿Buzz"
turning our 2-element list into a 3-element list. You might be wondering “what is being prepended to our list here?” and the answer is, the number each
passed in originally. Remember that we passed in each
number of our range (in our example 1+↕100
). Let’s play this through with one number, say 3
:
The first part will check if it is divisible by 3 or 5:
0=3‿5|⊢
Which for 3 will result in 1‿0
.
The next bit is kind of the core or the clever part of this FizzBuzz solution:
(¬∨´)⊸∾1‿0
⟨ 0 1 0 ⟩
So, what does this actually do? The not or/reduce
basically says: “set this to one if all the elements of the list are zero”. And why would we want that? Because for the FizzBuzz problem we will show the original number if it is not divisible by 3 or 5.
Remember how we prepended the original number to "Fizz"‿"Buzz"
? If we expand the replicate/swap
we will have this in the end:
0‿1‿0 / 3‿"Fizz"‿"Buzz"
⟨ "Fizz" ⟩
Now, if we have a number that would not be divisible by 3 or 5 we would have this situation:
1‿0‿0 / 2‿"Fizz"‿"Buzz"
⟨ 2 ⟩
And what if we have a number that is divisible by 3 and by 5?
0‿1‿1 / 15‿"Fizz"‿"Buzz"
⟨ "Fizz" "Buzz" ⟩
I hope it starts to slowly make a bit more sense by now.
The last bit, ∾´
is actually pretty straightforward, if we leave it out we will in the end have a list of lists, but we want to flatten the result into a simple vector instead:
Without join/reduce
:
(∾⟜"Fizz"‿"Buzz"/˜((¬∨´)⊸∾0=3‿5|⊢))¨1+↕15
⟨ ⟨ 1 ⟩ ⟨ 2 ⟩ ... ⟨ "Fizz" "Buzz" ⟩ ⟩
And with join/reduce
:
(∾´∾⟜"Fizz"‿"Buzz"/˜((¬∨´)⊸∾0=3‿5|⊢))¨1+↕15
⟨ 1 2 "Fizz" 4 "Buzz" "Fizz" 7 8 "Fizz" "Buzz" 11 "Fizz" 13 14 "FizzBuzz" ⟩
I can’t recommend the BQN docs enough, they are a great read! Here are the links to each of the primitives we have used today:
And if you want to know more about Array Programming in general (and a lot of motivation to dig deeper): The Array Cast is a very fun podcast!