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.
(One of possible forms of the fizz-buzz question).
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.
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 asys_StrBuilder
instance, put*
methods andnewLine
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.
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?
Thanks for the question (the first one BTW).
Sure, Argentum can do graphs (and with cycles of course). My concern is not data structures but rough and sketchy runtime library and the absence of syntax sugar.
This will make the example a bit messy.
I will make the second tutorial about graphs.
Done, graph tutorial is here: https://aglang.org/index.php/2023/04/13/tutorial-2-graphs-with-loops/
I chosen to find cycles instead of path finding because how else can I emphasize more the legality of cycles in Argentum data structures.