Day 2: 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.

Check out this Rust code (from RosettaCode.org):

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 do you reckon this Rust does? How would you rate its cognitive comfort? WAT the hell is 0u32 anyway?

Sadly, the happy path of this Rust program is hidden by all the imported libraries, type handling, scope changes and variables etc. Each of these adds a little bit of wattage to the overall program. Fully understanding it requires cognitively juggling all the intermediate variables (e.g., snooze_time, local_word, rng and rng_range etc) while mentally stepping through the program line-by-line.

How can we improve the cognitive comfort of Rust?

Rewrite it in Raku!

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

You get the gist here: race through a list of words in parallel batches and sleep for a random time before saying a word.

The Raku version is more cognitively comfortable: less lines of code, no external libraries and only two variables (@words and the topic $_). Less mental juggling is required and working memory is freed to facilitate flow.

It’s even possible to code in just one line:

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

But this adds a potential WAT. Sure, the one line version comes with bragging rights, ‘Look Ma, only one line!’, but it’s less understandable than the two line version. It’s not necessarily obvious to Rosetta Code readers that the circumfix operator “< >” constructs a white-space separated word list. Adding the @words variable, however, in the two line version aids understanding. It’s clear @words is a variable and .race() is a method acting on it.

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

Subtle decisions like this are required for flow-friendly code. Optimising a program for flow requires adjusting its cognitive load to make it easier for yourself and others to understand. The computer doesn’t care if a variable is called @r2d2c3pO or @star-wars-droids – but it makes a big difference to the poor hoomans.

Optimising code for flow is a big topic. Let’s focus on just one aspect of programming: variables. Good variable names encapsulate concepts in the problem domain, cutting through complexity, for programmer and maintainer alike. Unlike most languages, Raku variable names include extra flow-friendly features: sigils and twigils.

Sigils, are symbols (i.e., $ @ % &) that tip off the reader to the underlying nature of the variable: $scalar, @positional, %associative or &code. For example:

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)

Most programming languages have bare, sigil-less variables. To fully understand a variable in these languages the programmer typically needs to trace backwards to where the variable is first declared. This cognitive cost increases with the distance between the variable and its declaration.

Sigils in Raku help reduce this cost by signalling the nature of the variable wherever it’s used. For example, whenever you encounter a variable that begins with the @ sigil you know the variable is Positional, Iterable and its elements are accessible with a subscript (e.g., @students[0]).

Likewise, for an %associative-variable (e.g., %cs100-scores) the contents are Associative, Iterable and the elements are accessed like so:

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

Twigils, or secondary sigils, further clarify the scope of a variable. For example, the twigil * denotes a dynamic variable available anywhere in your Raku program.

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

Upper-cased by convention, variable values twigil-ed with * are looked up when accessed:

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

Other variables, set at compile time, are denoted with the ? twigil. For example:

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

The ^ twigil is used for positional parameters in blocks and subroutines. Variables containing the ^ twigil are scoped just to the current sub or block and act like parameter placeholders. For example:

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 is used for named parameter placeholders in subs and blocks. For example:

sub full-name {
return join(' ', $:first, $:last);
}
say full-name(last => 'Wall', first => 'Larry'); # Larry Wall

Inside a class the ! twigil denotes access to private attributes.

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

The combination of sigils and twigils in variable names gives the programmer an instant understanding of a variable’s scope and how to access it.

To a new Raku programmer the sigils and twigils might appear like line-noise but once over the small learning curve (see above) they help the programmer to go forwards rather than backwards.

Different variables look different in Raku, which, despite the initial learning curve, reduces their overall wattage and helps flow-ability. Sometimes, however, you need to delve deeper into a variable. “What is this?” Just use WHAT to find out its type:

note $*CWD.WHAT; # (Path)

To get the gist of what the variable is:

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

To see a variable’s contents call raku on it or pass it to the builtin data dumper function (dd):

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 does a $*CWD variable work like that? Just ask WHY

note $*CWD.WHY;
No documentation available for type 'IO::Path'.
Perhaps it can be found at https://docs.raku.org/type/IO::Path

Or even HOW the variable works – it’s Higher Order Workings, can be accessed with ^ like so:

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

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

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

Happy Christmas.

9 thoughts on “Day 2: Less Variable Wattage = More Flow

  1. Great post!

    On related issue that I keep coming back to is that WATs are very reader-specific (and depend on experience/familiarity). For example, you mentioned the 0u32 as a WAT, but it wouldn’t be to someone used to Rust’s type system.

    Switching to the Raku code, I’d be curious to know how you’d score the following 2 variants of your second line:
    ①: @words.race(:1batch).map: { sleep rand; say $_ };
    ②: @words.race(:batch(1)).map: -> $word { sleep rand; say $word };

    ① is simpler and (imo) more intuitively readable (I’d verbalize what’s going on as “there’s one batch”, not as “a batch of one”). But, it creates a WAT for any reader who isn’t familiar with the :1key Pair syntax. On the other hand, ② is (again, imo) less readable – it adds the needless cognitive load of an extra name, just to discard it. But it avoids using a topic variable, which can be a WAT to any new Rakoons coming from languages that don’t have them (i.e., most of them).

    I guess it seems to me that figuring out who you’re writing to is a huge part of the battle here. Is that how you think about it, or is there some way to think about WATs a bit less subjectively?

    Liked by 2 people

    1. Yes – good points. Even though the :1key Pair syntax is unusual for me I still find ① to have lower wattage. There’s less things to juggle in ①. Humans have a limit to their working memory so keeping slots free for your audience helps flow.

      I agree that for someone new to Raku the topic $_ is likely to be a flow stopper. It depends where on the learning curve you meet your audience. Sometimes it’s good to actively help someone up the learning curve – acquiring new knowledge and skills in the face of a challenge is part of feeling flow too.

      Thanks for the reminder about the :1key Pair syntax.

      Liked by 1 person

      1. I think everyone who is reading Raku code should be aware of the topic variable, because it’s one of the most useful (and widely used) constructs in the language (and even in Perl — the “other” topicalized language)…

        Liked by 2 people

  2. I made the anonymous commment “Excellent” because I couldn’t take the time to login. As an advanced “senior citizen” Rakuun, I really appreciate this article. Raku is definitly low-WATage for me, and it is the Perl I always wanted back in my $working days. Thanks for an excellent article, and I hope to use it with some reluctant-to-try-Raku friends.

    Liked by 2 people

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.