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.
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).
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 $_).
<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 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 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 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);
}
}
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
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.