So a couple of days ago I was taken with the fact that you could make the Raku programming language appear to do I/O somewhat like C++:
Or you can even go full C++ if that takes you:
— Jonathan Stowe (@gellyfish) June 17, 2022
use isms; sub infix:«<<»( IO::Handle $h, Cool $v ) { $h.print: $v; $h }; sub term:<cout> { $*OUT }; sub term:<endl> { "\n" }; cout << "Whatever" << endl;#rakulang
Now it occurs to me that this may be somewhat opaque to someone not familiar with Raku, so, rather than explain it in a bunch of tweets, I'll give a quick breakdown here
First up the weird looking "use isms;": Raku has some heritage from languages that have some different operators for common operations such as "left shift", and to help avoid common mistakes due to these differences, rakudo goes out of its way to provide a helpful error message when the programmer uses one of the common operators that have different meanings or none at all in Raku, So for example if one was to use the "left shift" (or C++ stream extraction in this case,) operator one would get something like:
[jonathan@menenius hesabu]$ raku -e 'my $a = 1; say $a << 2'
===SORRY!=== Error while compiling -eUnsupported use of << to do left shift. In Raku please use: +< or ~<.
at -e:1
------> my $a = 1; say $a <<⏏ 2
The "use isms;" pragma turns off this behaviour, so you get a different error message, helpful in a different way, but assuming you know what you are doing:
[jonathan@menenius hesabu]$ raku -e 'use isms; my $a = 1; say $a << 2'
===SORRY!=== Error while compiling -e
Variable '&infix:«<<»' is not declared. Perhaps you forgot a 'sub' if
this was intended to be part of a signature?
at -e:1
------> use isms; my $a = 1; say $a ⏏<< 2
Which kind of gives us a clue to what we need to do to implement something like the C++ stream extraction operator.
Raku allows you to define new operators (or define new candidates for some existing operators with different meanings for different "arguments",) by defining a new subroutine that specifies the operator's behaviour for the specific arguments. In the case of the "<<" we want to define an infix operator (that is one that has two "arguments" to the left and right of the operator,) so one define something like the C++ stream extraction operator something like:
sub infix:«<<» ( IO::Handle $h, Cool $v ) { $h.print: $v; $h }
That is to say a subroutine that will be called when the operator "<<" is used with an IO::Handle (which is the base class of most IO classes,) on the left hand side and a Cool (which is the base class for most of the built in Scalar types such as Str, Int etc,) on the right hand side. It returns the LHS handle so an expression using this operator can in turn be used on the LHS of further uses of the operator and so on.
So this allows us to do:
$*OUT << "foo";
(omitting the actual definition of the operator and the "use isms;" that allows it to work.)
The "$*OUT" is a "Dynamic Variable", that is one that can be redefined by assignment within a runtime scope (retaining the original value in outer scopes,) by default it is the IO::Handle for STDOUT.
But in C++ the stream object is "cout" so to be more like C++ we need to use that bareword symbol rather than Raku's dynamic variable to represent the target handle.
Now we could express that as a regular subroutine, or as a sigil-less constant but the former has constraints on where it can be used without (even empty,) argument list, and the latter won't retain the dynamic behaviour of the underlying value (being set at compile time.) So this will be defined as a "term".
A "term" is defined as a subroutine without any arguments that can be used anywhere that a value can be used in Raku code (builtin examples include now and pi,) so we can define "cout" simply as
sub term:<cout> { $*OUT }
Which allows cout to be used anywhere that a value can be used without needing parentheses and will still allow the underlying value to be set within the runtime scope.
Obviously then the C++ symbol "endl" can be defined simply as:
sub term:<endl> { "\n" }
And there you have it:
use isms;
sub infix:«<<»( IO::Handle $h, Cool $v ) { $h.print: $v; $h };
sub term:<cout> { $*OUT };
sub term:<endl> { "\n" };
cout << "Whatever" << endl;
Making the Raku programming language more familiar to C++ programmers.