Tutorial #1. Fizz-Buzz.

Code updated to v0.0.2: added module imports.

Preparations

Setup VSCode environment as shown here: how-to-play-with-argentum-in-vscode.

Right-click on src folder and select new file:

Type fizz_buzz.ag

Question

Fizz-buzz is a famous coding interview question. Usually it sounds as follows:

Write to the console N lines for numbers from 1 to N.

If a number is divisible by 3 write the word "fizz" instead of the number.
If the number is divisible by 5 write the word "buzz" instead.
If the number is divisible by both 3 and 5 (like 15) write both words "fizz buzz".
Overwise write the number itself.

(One of possible forms of the fizz-buzz question).

Solution

We'll need to loop by the range of numbers and perform some string manipulations. Let's import this dependencies from other moodules:

using sys { String, StrBuilder, log }
using string;
using utils{ forRange }

Line "using sys { String; StrBuilder; log; }" declares a dependency on module sys and imports String and StrBuilder classes and the log function from this module.

So far Argentum has one built-in loop and a number of functions in utils module that makes more specialized loops.

forRange(1, 101) `i {
   log("fizz");
}

(BTW you can press Ctrl+Shift+B to build and run this example).

forRange is a function that takes:

  • The range boundaries (left - inclusive, right - exclusive)
  • A lambda that will be called for each integer in range.

Syntax `name `name { code } defines lambdas.

There are other ways to define lambda:

`name `name expression
`name { expressions }
\expression

If lambda is the last parameter of the function call, it can be passed outside () and in this case an additional form {expressions} is allowed. BTW, if a lambda form goes with no parameters, but body expressions use a special name `_`, one implicit parameter is created automatically. But if at call site it becomes known that lambda has no parameters, this implicit parameter gets removed and all references to it become the references to outside `_` name if it exists, or error otherwise.

forRange(1, 101) `i {
   log("fizz")
}
// or
forRange(1, 101) `i log("fizz");
// or
forRange(1, 101, `i log("fizz"));

All the above code just prints "fizz" 100 times.
Not what we needed, so let's add some more logic.

The sys module defines a StrBuilder class (and string module extends it). StrBuilder concatenates strings, characters, numbers, adds new lines, operates with text positions, and converts everything to the immutable string:

s = StrBuilder.putStr("Hello").putInt(42).putCh('!').newLine().toStr();
sys_log(s);

In this example:

  • Expression StrBuilder - creates a sys_StrBuilder instance,
  • put* methods and newLine method populate this string builder with different fragments,
  • toStr - resets builder and extracts its contents as a string.

Btw the expression "Hello{42}!{CR}" also creates an instance of StrBuilder and calls the above mentioned methods on it.

Let's make use of the string builder in our fizz-buzz example:

b = StrBuilder;

forRange(1, 101) {
   _ % 3 == 0 ? b.putStr("fizz") : b.putInt(_);
   log(b.newLine().toStr());
}

This example shows the { use(_) } lambda syntax.

Also this example shows the construction that looks and works like a ternary operator: A ? B : C;
But it is a little bit more than just a ternary operator. It is a pair of two binary operators: (A ? B) : C;

We'll look at these constructions deeper in a separate tutorial, and for now, let's just rewrite it this way:

b = StrBuilder;

forRange(1, 101) {
   _ % 3 == 0 ? b.putStr("fizz");
   b.putInt(_);
   log(b.newLine().toStr());
}

Construction A ? B executes B only if A is true.
So in this example we print fizz conditionally and append the number unconditionally.
At this point we know everything we need to write the correct code:

using sys { String, StrBuilder, log }
using string;
using utils{ forRange }

b = StrBuilder;

forRange(1, 101) {
   _ % 3 == 0 ? b.putStr("fizz");
   _ % 5 == 0 ? b.putStr("buzz");
   b.pos == 0 ? b.putInt(_);     // if builder is empty, add a number
   log(b.newLine().toStr());
};

There are many other equally reasonable ways to solve this question:

using string;
using utils;

utils_forRange(1, 101, `i{
    b = sys_StrBuilder;
    i % 5 == 0
        ? b.putStr(i % 3 == 0 ? "fizzbuzz" : "fizz")
        : (i % 3 == 0 ? b.putStr("bazz") : b.putInt(i));
    sys_log(b.newLine().toStr());
});

Alternatively this can be done with itos function.
Right now Argentum stdlib doesn't have such function, let's define one:

fn itos(i int) String {
    StrBuilder.putInt(i).toStr()
}
// or just 
fn itos(i int) String { "{i}" }

This function takes an integer argument and returns a sys_String object (this is the same string type as "Quoted" string literals).

Please pay attention to the absence of ; at the end of the function body. The last expression in the function body generates the function result. And ; creates an empty expression returning void. So if ; were added here, compilation would fail with "expected String, not void" error message.

The fizz-buzz solution could also be written using string interpolation:

using sys { String, StrBuilder, log }
using string;
using utils{ forRange }
const LN = utf32(10);

forRange(1, 101) `i {
    log(i % 5 == 0
        ? (i % 3 == 0 ? "fizzbuzz{LN}" : "fizz{LN}")
        : (i % 3 == 0 ? "buzz{LN}"     : "{i}{LN}"));
}

So this is it for now. See ya in the next tutorials.

3 Comments

  1. Good, this language can do loops and conditions.
    What about data structures, especially ones that are not trees?
    What about pathfinding in a graph with loops?

Leave a Reply

Your email address will not be published. Required fields are marked *