DEV Community

Tomasz Wegrzanowski
Tomasz Wegrzanowski

Posted on • Updated on

100 Languages Speedrun: Episode 27: Whenever

Whenever is a little known esoteric language where the program is a todo list of items.

Any actionable item can be executed, in whichever order the program feels like.

Oh and there are no variable, no functions, and none of the usual stuff, so it will be fun!

Hello, World!

Hello, World! is very easy - just one todo item to print "Hello, World!". As there's no other items on program's todo list, that's what's going to happen.

1 print("Hello, World!");
Enter fullscreen mode Exit fullscreen mode

It does exactly what we'd expect:

$ whenever hello.txt
Hello, World!
Enter fullscreen mode Exit fullscreen mode

Any Order

Let's

1 print("Cats");
2 print("Are Better Than");
3 print("Dogs");
Enter fullscreen mode Exit fullscreen mode

There's 6 possible outputs, here's two of them:

$ whenever anyorder.txt
Dogs
Are Better Than
Cats
$ whenever anyorder.txt
Cats
Are Better Than
Dogs
Enter fullscreen mode Exit fullscreen mode

Loop

Here's a simple loop that prints 1 to 10, without variables.

1 2#9;
2 defer (1) print(11 - N(2));
Enter fullscreen mode Exit fullscreen mode

What the hell is going on here?

  • 2#9 means put todo task 2 on the list 9 more times.
  • defer (1) means that the task 2 cannot be done if task 1 is still pending
  • N(2) checks how many times task 2 is on todo list, including current one. The first time it's executed N(2) is 10.
  • So 11 - N(2) starts as 11 - 10, that is 1
  • next time line 2 is executed, N(2) is 9, so 11 - N(2), that is 2, is printed
  • and so on until 11 - 1, that is 10, is printed, and there's nothing more to do

Odd Even

Before we do the Fizz Buzz, let's try something simpler - Odd Even loop.

It's very easy to modify the original program to print "1 is odd", "3 is odd" and so on until some predefined value. We need to adjust the formula to be A - N(lineno) * 2 to step by 2, for some value of A (we could either calculate with math, or adjust until we find the right value).

It's just as easy to modify the original program to print "2 is even", "4 is even" and so on.

The only difficulty is to make the loops run in sync, and for that they should look at each other's counters in defer condition:

1 2#4,3#4;
2 defer (1 || N(2) < N(3)) print((11 - N(2) * 2) + " is odd");
3 defer (1 || N(2) == N(3)) print((12 - N(3) * 2) + " is even");
Enter fullscreen mode Exit fullscreen mode

Which generates:

$ whenever oddeven.txt
1 is odd
2 is even
3 is odd
4 is even
5 is odd
6 is even
7 is odd
8 is even
9 is odd
10 is even
Enter fullscreen mode Exit fullscreen mode

Fizz

To keep complexity low, let's do the Fizz - the FizzBuzz version with just the Fizz rule.

We could just do 3 interlocking loops. To keep things simple let's keep them at same number of iterations, so it prints numbers 1 to 18:

1 2#5,3#5,4#5;
2 defer (1 || N(2) < N(4)) print(19 - N(2) * 3);
3 defer (1 || N(3) == N(2)) print(20 - N(3) * 3);
4 defer (1 || N(4) == N(3)) print("Fizz");
Enter fullscreen mode Exit fullscreen mode

Which outputs:

$ whenever fizz.txt
1
2
Fizz
4
5
Fizz
7
8
Fizz
10
11
Fizz
13
14
Fizz
16
17
Fizz
Enter fullscreen mode Exit fullscreen mode

We could do 15 of such loops for FizzBuzz, and add some +1s to condition to make it print unequal number of them, but how about we try something else?

Emulating Variables

This code is much longer, but it might be a lot easier to understand:

1 2#-1,3#-1,4#-1,6#-1,7#-1;
2 2;
3 defer(1) print(N(2));
4 defer(1) print("Fizz");
5 defer(1 || 3 || 4) 2,3,6;
6 defer(1 || 3 || 4) 2,3,7;
7 defer(1 || 3 || 4) 2,4,5;
Enter fullscreen mode Exit fullscreen mode

It produces infinite Fizz:

whenever fizz2.txt | head -n 20
1
2
Fizz
4
5
Fizz
7
8
Fizz
10
11
Fizz
13
14
Fizz
16
17
Fizz
19
20
Enter fullscreen mode Exit fullscreen mode

There's much less magic here, and it's very systematic way to do programming:

  • line 1 initializes all counters. They start as 1,2,3,4,5,6,7, but we only want to have 5 as todo list, so we remove everything else.
  • to make sure initializer runs, everything except 1 and 2 has defer(1) condition.
  • 2 2; is a todo list that just reschedules itself when executed, so we basically created a variable. As it does nothing there's no need to put defer(1) condition on it.
  • Whenever doesn't allow mixing print statements and line statements, so todo items 3 and 4 do various kinds of printing. If any printing is todo, everything after that is deferred.
  • and everything that follows is loop condition. 5 doing 2,3,6 is like N(2)+=1 (2), print(N(2)) (3), and goto 6 (6).

FizzBuzz

So let's try to do FizzBuzz:

1 2#-1,3#-1,4#-1,5#-1,6#-1,8#-1,9#-1,10#-1,11#-1,12#-1,13#-1,14#-1,15#-1,16#-1,17#-1,18#-1,19#-1,20#-1,21#-1;
2 2;
3 defer(1 || N(2) > 100) print(N(2));
4 defer(1 || N(2) > 100) print("Fizz");
5 defer(1 || N(2) > 100) print("Buzz");
6 defer(1 || N(2) > 100) print("FizzBuzz");
7 defer(1 || 3 || 4 || 5 || 6 || N(2) > 100) 2,3,8;
8 defer(1 || 3 || 4 || 5 || 6 || N(2) > 100) 2,3,9;
9 defer(1 || 3 || 4 || 5 || 6 || N(2) > 100) 2,4,10;
10 defer(1 || 3 || 4 || 5 || 6 || N(2) > 100) 2,3,11;
11 defer(1 || 3 || 4 || 5 || 6 || N(2) > 100) 2,5,12;
12 defer(1 || 3 || 4 || 5 || 6 || N(2) > 100) 2,4,13;
13 defer(1 || 3 || 4 || 5 || 6 || N(2) > 100) 2,3,14;
14 defer(1 || 3 || 4 || 5 || 6 || N(2) > 100) 2,3,15;
15 defer(1 || 3 || 4 || 5 || 6 || N(2) > 100) 2,4,16;
16 defer(1 || 3 || 4 || 5 || 6 || N(2) > 100) 2,5,17;
17 defer(1 || 3 || 4 || 5 || 6 || N(2) > 100) 2,3,18;
18 defer(1 || 3 || 4 || 5 || 6 || N(2) > 100) 2,4,19;
19 defer(1 || 3 || 4 || 5 || 6 || N(2) > 100) 2,3,20;
20 defer(1 || 3 || 4 || 5 || 6 || N(2) > 100) 2,3,21;
21 defer(1 || 3 || 4 || 5 || 6 || N(2) > 100) 2,6,7;
22 defer(N(2) <= 100) 2#-N(2),3#-N(3),4#-N(4),5#-N(5),6#-N(6),7#-N(7),8#-N(8),9#-N(9),10#-N(10),11#-N(11),12#-N(12),13#-N(13),14#-N(14),15#-N(15),16#-N(16),17#-N(17),18#-N(18),19#-N(19),20#-N(20),21#-N(21);
Enter fullscreen mode Exit fullscreen mode

It works perfectly:

$ whenever fizzbuzz.txt
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz
16
17
Fizz
19
Buzz
Fizz
22
23
Fizz
Buzz
26
Fizz
28
29
FizzBuzz
...
Enter fullscreen mode Exit fullscreen mode

It's a lot longer than an usual FizzBuzz, but really not that much more complicated:

  • initializer in 1 only leaves 7, 22, resets everything else
  • 2 is the only variable
  • 3 to 6 are various print statements
  • printing does not progress if initializer is todo, or if N(2) > 100
  • 7 to 21 are various loop parts - each increments the variable, schedules some printing and goes to next step of the loop
  • printing does not progress if initializer is todo, or any printing is todo, or if N(2) > 100
  • finally 22 is exit condition - if N(2) > 100 it's the only thing that can execute and it sets all other counts to 0.

Another Fizz

Let's try a different approach, starting with just Fizz part again.

1 2#-1,3#-1,4#-1,5#-1,6#-1;
2 2;
3 3;
4 defer(1 || (N(5) + N(6)) != 1) 5#-N(5),6#-N(6);
5 defer(1 || N(3) == 0) print(N(2));
6 defer(1 || 3) print("Fizz");
7 defer(1 || 4) 2,3,-3#((N(3)/3)*3),4,5,6,7;
Enter fullscreen mode Exit fullscreen mode

What's going on here?

  • initializer in 1 only leaves 7, resets everything else
  • 2 is variable N
  • 3 is variable N % 3
  • 4-6 are the print system. To issue print command all of them should be scheduled exactly once.
  • 4 sees that one of the print commands finished (so only 1 is remaining), so it clears all of them.
  • 5 and 6 are various print statements, depending on N(3), that is N % 3
  • condition 3 is shortcut for N(3) != 0, documentation says !3 should work for the opposite condition, but isn't actually implemented
  • If we could do print and change in the same statement, we could get away with just 6 defer(1 || 3) print("Fizz"),-5; and not need 4, but that's just part of Whenever design.
  • 7 is our loop iteration
  • 7 increments 2 (N)
  • 7 increments 3, then it decrements it by ((N(3)/3)*3). That expression is 0 if N(3) is less than 3, then it becomes 3. So as a result N(3) loops 0, 1, 2, 0, 1, 2, etc.
  • 7 schedules printing with 4, 5, 6 - print system will run either (5 then 4) or (6 then 4), depending on N(3)
  • 7 schedules itself to run again, but it will wait for the print system.

Another FizzBuzz

1 2#-1,3#-1,4#-1,5#-1,6#-1,7#-1,8#-1,9#-1;
2 2;
3 3;
4 4;
5 defer(1 || (N(6) + N(7) + N(8) + N(9)) != 3) 6#-N(6),7#-N(7),8#-N(8),9#-N(9);
6 defer(1 || N(3) == 0 || N(4) == 0) print(N(2));
7 defer(1 || 3 || N(4) == 0) print("Fizz");
8 defer(1 || N(3) == 0 || 4) print("Buzz");
9 defer(1 || 3 || 4) print("FizzBuzz");
10 defer(1 || 5 || N(2) >= 100) 2,3,-3#((N(3)/3)*3),4,-4#((N(4)/5)*5),5,6,7,8,9,10;
11 defer(1 || 5 || N(2) < 100) -2#N(2),-3#N(3),-4#N(4),-10#N(10);
Enter fullscreen mode Exit fullscreen mode

It follows all patterns from the previous Fizz - with just extra variable for N%5 (4), a few more prints (5-9), loop exit condition (11), and extra check in main loop iteration for loop exit (10).

Should you try Whenever?

I stumbled upon this language entirely by accident, I've never seen anything like it, and as far as I can tell it's virtually unknown even among esoteric language lovers.

The existing Java interpreter is a bit low quality:

  • it doesn't compile on latest Java without some trivial tweaks (enum is now a keyword so quite search and replace)
  • it doesn't have good debug output (what we want is state)
  • it doesn't optimize away NOPs, so it can be extremely slow, trying to execute do-nothing code (variables like 2 2; or code with defer checks preventing it from running).
  • it doesn't even fully follow its own spec, especially lacking ! operator

But language itself is just brilliant. I hope someone writes a better interpreter for it, with in-browser visualization. To be honest I like it so much I might even do it at some point.

I definitely recommend giving it a try.

Code

All code examples for the series will be in this repository.

Code for the Whenever episode is available here.

Oldest comments (0)