Saturday 18 June 2022

Make the Raku programming language familiar to C++ programmers

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++:

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 -e

Unsupported 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. 

Saturday 5 February 2022

Making a home thermostat with a Raspberry Pi and the Raku Programming Language. Part 1: getting the temperature.

A few weeks ago I had a moment of hubris on Twitter

I was particularly interested in the idea of making a thermostat with a Raspberry Pi but using Raku instead of Python. Being somewhat impatient and impulsive I bought all the parts I thought I would need from Amazon and set about thinking about what the software would need to do.

The digital thermometer used (the DS18B20 from Maxim Integrated,) seemed to be the natural place to start so I wrote a Raku module to read the temperatures from one or more of the devices. As this may be more generally useful in another programme and I can leave you with a somewhat useful programme without all the other parts, we'll start with this part.

The DS18B20 is a digital thermometer that uses the Dallas 1-Wire interface, the nature of the interface means that a number of the devices can be attached using only a single GPIO pin on the Raspberry Pi. The 1-Wire protocol in general and thermometers specifically are fairly well supported by the Raspberry Pi OS, presenting each attached device as nodes in the sysfs

Assuming you have the RPi::Device::DS18B20 installed on your Raspberry Pi you will only need one or more DS18B20 devices (I'm using the encapsulated "waterproof" ones with longer wires, it seems it's just as cheap to buy five of these from Amazon than the bare chips,) a 4.7K resistor and for convenience a solderless breadboard and some patch cables.

The circuit itself is really quite simple:

Additional thermometers can be added by providing the power and linking the data lines (the yellow wire.)

With this circuit you can run the synopsis example:

And you should get some output like:

28-012113620c31:	20.562
28-03213194ea4f:	17.25

I have two thermometers attached. The first thing you've probably noticed is the "name" of the devices, this device ID is basically baked in the chip at manufacture and not very human friendly so we'll providing a mapping of these to more useful names as we build the application.

For the first part of this we'll create a web application with Cro::HTTP which displays the temperatures dynamically, and for this kind of purpose RPi::Device::DS18B20 provides an asynchronous interface via a which provides the readings periodically:

This emits a Reading object for each attached device periodically, because the thermometers may take different times to produce a result they may not be in any particular order but this means that, unlike the first example, you don't have to wait for a preceding reading which is taking longer. This is ideal for a web application where we can use Server Sent Events with EventSource::Server and some javascript on the client side to update the web page when new reading become available.

Given that we already have a Supply from the RPi::Device::DS18B20 and the EventSource::Server can take a Supply of the events to be emitted we can do the majority of the work by mapping one Supply to one suitable to pass to the EventSource::Server:

Here we are mapping the Reading objects emitted by the RPi::Device::DS18B20 Supply to a custom class TemperatureEvent which contains the mapped name of the device, the device id and the temperature. This class does the role JSON::Class so the objects can be trivially serialised to JSON in the next step where the JSON representation of the object is mapped into a EventSource::Server::Event with a type of 'reading'.

Here I've used a plain Hash to lookup the user friendly name from the device id,for a larger programme it may be more suitable in some configuration or other storage. If you do have more than thermometer device the easiest way to derive the lookup is to apply some source of warmth to each device in turn (a hot liquid or even holding it in your hand,) while running the first example each time, noting the device id with the raised temperature (and possibly labelling for future reference.)

And that is basically that, all that remains is to provide the out-supply of the EventSource::Server object as the content of an appropriate route in the Cro::HTTP application and provide a web-page in which to display the readings and we're done:

The index.html is simply:

Which basically arranges to read the events from the stream and update the table as appropriate. It could obviously be a bit prettier but we'll leave that for later.

In the next post or posts we'll update this application to do the other part of the thermostat application which is to turn relays on and off in order to control the central heating, but as I'm taking a slightly different approach than the post that inspired this I'll need to write a library to drive an MCP23017 first.

The code of the example can be found on Github