Less Variable Wattage = More Flow

Finding flow while coding is sometimes tricky to do - it's even trickier when encountering ‘someone else's code’. We've all had the experience of reading code and crying, “WAT?!”.

Working with high ‘wattage’ code is not just unpleasant, it costs time and money. The more WATs a program contains, the scarier it is, and sadly, fear is a flow stopper.

By contrast, writing low wattage code can facilitate flow by keeping things cognitively comfortable for yourself and other programmers. Let's start with some high wattage code and Raku-ify it.

Example: Rust code

Here's Rust code from RosettaCode.org for concurrent computing:

extern crate rand;

use std::thread;
use rand::thread_rng;
use rand::distributions::{Range, IndependentSample};

fn main() {
    let mut rng = thread_rng();
    let rng_range = Range::new(0u32, 100);
    for word in "Enjoy Rosetta Code".split_whitespace() {
        let snooze_time = rng_range.ind_sample(&mut rng);
        let local_word = word.to_owned();
        std::thread::spawn(move || {
            thread::sleep_ms(snooze_time);
            println!("{}", local_word);
        });
    }
    thread::sleep_ms(1000);
}

What does this Rust code do? The program spawns threads for each word with random sleep times. However, its cognitive load is high due to imported libraries, type handling, scope changes, and multiple intermediate variables (snooze_time, local_word, rng, and rng_range).

Raku equivalent

my @words = <Enjoy Rosetta Code>;
@words.race(:batch(1)).map: { sleep rand; say $_ };

This version is more cognitively comfortable: fewer lines of code, no external libraries, and only two variables (@words and the topic $_).

One-line version

<Enjoy Rosetta Code>.race(:batch(1)).map: { sleep rand; say $_ };

While this one-liner works, it creates a potential WAT. The circumfix operator < > constructs a white-space separated word list, which isn't obvious to new readers. The two-line version is preferable because it clarifies that @words is a variable and .race() is a method acting on it.

Optimizing for flow: variables

Optimizing code for flow requires adjusting its cognitive load. Good variable names encapsulate concepts in the problem domain. Unlike most languages, Raku variable names include flow-friendly features: sigils and twigils.

Sigils

Sigils are symbols ($, @, %, &) that indicate the variable's nature:

my $student         = 'Joe Bloggs';     # scalar: (Str)
my $total-students  = 3;                # scalar: (Number)
my @students        = <Joe Mary Dave>;  # positional: (Array)
my %cs100-scores    = (                 # associative: (Hash)
                       'Joe'   => 87,
                       'Mary'  => 92,
                       'Dave'  => 63,
                      );
my &hello           = sub { say "hi"; }; # code: (Sub)

Sigils reduce cognitive cost by signaling a variable's nature wherever it's used. When you encounter a variable with the @ sigil, you know it's Positional, Iterable, and its elements are accessible with subscripts (e.g., @students[0]).

For associative variables (e.g., %cs100-scores), contents are accessed like this:

%cs100-scores{'Joe'};   # 87
%cs100-scores<Mary>;    # 92

Twigils (secondary sigils)

Twigils clarify variable scope.

The * twigil: denotes dynamic variables available anywhere:

$*CWD   # the current working directory
@*ARGS  # a list of command-line arguments
%*ENV   # environment variables

Values are looked up when accessed:

say $*CWD.Str;  # /home/nige/raku/advent-2022
chdir('/tmp');
say $*CWD.Str;  # /tmp

The ? twigil: set at compile time:

say $?FILE;    # test.raku - filename
say $?LINE;    # 2         - line number
$?LINE = 100;  # BOOM      - immutable, can't modify

The ^ twigil: positional parameters in blocks and subroutines:

sub full-name {
    # the Unicode order of the variable names matches
    # the positional order of the parameters.
    return join(' ', $^b, $^a);
}

say full-name('Wall', 'Larry');  # Larry Wall

The : twigil: named parameter placeholders:

sub full-name {
    return join(' ', $:first, $:last);
}

say full-name(last => 'Wall', first => 'Larry'); # Larry Wall

The ! twigil: private attributes in classes:

my class Student {
    has $.first-name;
    has $.last-name;
    method full-name() {
        return join(' ', $!first-name, $!last-name);
    }
}

Introspection

To understand variables better, Raku provides several tools.

WHAT: find a variable's type.

note $*CWD.WHAT;    # (Path)

gist: get what the variable is.

note $*CWD.gist;    # "/home/nige/raku/advent-2022".IO

raku or dd: see variable contents.

note $*CWD.raku;  # also dd($*CWD)

IO::Path.new("/home/nige/raku/advent-2022",
 :SPEC(IO::Spec::Unix),
:CWD("/home/nige/raku/advent-2022"))

WHY: ask for documentation.

note $*CWD.WHY;

No documentation available for type 'IO::Path'.
Perhaps it can be found at https://docs.raku.org/type/IO::Path

HOW: access Higher Order Workings with ^.

note $*CWD.^attributes;     # attributes it contains
note $*CWD.^methods;        # a full list of methods
note $*CWD.^roles;          # roles it does
note $*CWD.^mro;            # method resolution order

Conclusion

Raku variables include everything needed to understand how to use them. There's always an answer to the question, “WAT!?”

Introspection, sigils and twigils mean Raku variables are low wattage by design and that helps the code to flow.

Originally published 2022 on the Raku Advent Calendar.

← Back to articles