This is Part 3 in a series. You can start at the Intro here.

So far this Perl 6 wrapper class for matplotlib is going well. With the first graph in the gallery under my belt, I moved on to second example, `fill_demo.py`

. Here's the Python code

import numpy as np import matplotlib.pyplot as plt x = np.linspace(0, 1, 500) y = np.sin(4 * np.pi * x) * np.exp(-5 * x) fig, ax = plt.subplots() ax.fill(x, y, zorder=10) ax.grid(True, zorder=5) plt.show()

Being a rather simple graph, you might think that the Perl code would be fairly straight forward, and you'd be right. The only minor curveball is another numpy function `linspace`

, and that rather hairy looking operation for the `y`

value.

To reiterate what I've said in previous parts, I'm not familiar with numpy at all. Or at least, I wasn't before I started working on these graphs. I inserted a `print(x)`

into the Python code to see what `linspace`

did. In hindsight, the name is obvious; It simply creates a linearly spaced sequence of numbers. Here's a quick demonstration

>>> numpy.linspace(0, 1, 3) array([ 0. , 0.5, 1. ]) >>> numpy.linspace(0, 1, 5) array([ 0. , 0.25, 0.5 , 0.75, 1. ]) >>> numpy.linspace(0, 1, 6) array([ 0. , 0.2, 0.4, 0.6, 0.8, 1. ])

In the case of `x`

in the graph, it's a sequence of 500 numbers evenly spaced from 0 to 1. So how would I do this in Perl. It is a monotonic sequence, so maybe the Sequence operator can help us again. So far we've seen a couple simple Sequences. The most simple sequence defines the start of a number series, and Perl lazily generates the rest.

> 0, 2, 4, 8 ... 1024 (0 2 4 8 16 32 64 128 256 512 1024)

This is nice and all, but I had to define 4 numbers for Perl to understand that sequence. If I dropped the `8`

, then it would have generated a sequence of even numbers. Instead, I can define a function which calculates how the next value should be generated.

> 0, 2, * × 2 ... 1024 (0 2 4 8 16 32 64 128 256 512 1024)

Here we see our old friends, the Whatever `*`

. This time I'm asking for the Sequence starting `0, 2`

, then I define a mini-function that takes Whatever the last number in the Sequence was and multiplies is by 2. Sequences are a fascinating part of Perl 6 that could occupy a blog post all their own, but for now, that's enough of a foundation to create a simplified `linspace`

function.

sub linspace( $start, $end, $steps ) { my $step = ( $end - $start ) ÷ ( $steps - 1 ); return $start, * + $step ... $end; } linspace( 0, 1, 3 ); # Result: (0, 0.5, 1) linspace( 0, 1, 5 ); # Result: (0, 0.25, 0.5, 0.75, 1) linspace( 0, 1, 6 ); # Result: (0, 0.2, 0.4, 0.6, 0.8, 1)

This function does what I need, but it could stand to be a little more robust. I'll make a module to house this function, just in case I need it for future plots. I'll also add type constraints to the parameters, and multiple dispatch functions to handle steps of 0 or 1. I'm calling my library `Numpl`

. Any resemblance to actual libraries is purely coincidental.

class Numpl { proto method linspace( Real $start, Real $end, Int $steps ) { * } multi method linspace( $start, $end, 0 ) { Empty } multi method linspace( $start, $end, 1 ) { $start } multi method linspace( $start, $end, Int $steps ) { my $step = ( $end - $start ) ÷ ( $steps - 1 ); return $start, * + $step ... $end } }

So that was a very scenic tour around the Sequence operator, but now that we have a `linspace`

function, the rest is smooth sailing. Getting back to the plot, the final code now looks like this.

use Matplotlib; use Numpl; my $plt = Matplotlib.new; my @x = Numpl.linspace( 0, 1, 500 ); my @y = @x.map(-> $x { sin( 4 × π × $x ) × exp( -5 × $x ) }); my ( $fig, $ax ) = $plt.subplots(); $ax.fill( $@x, $@y, :zorder(10) ); $ax.grid( True, :zorder(5) ); $plt.show();

Oh yeah, there was that hairy looking operation for the `y`

values. To refresh your memory, the python looked like this

y = np.sin(4 * np.pi * x) * np.exp(-5 * x)

I can pretty much copy this operation exactly as it appears and put it inside the map. I've put the map operation on it's own line as I think this aids readability.

You might have also noticed I'm using proper `@arrays`

in this one, and there is a valid reason for this. Sequences are lazy, and typically can only be iterated over once. `x`

is iterated over in the map and that would prevent any later iterations. Here I've assigned the Sequence to a fully reified Array, which also means the Sequence is evaluated eagerly at assignment (ie. not lazily). As covered in part 2, Python doesn't like Perl arrays as positional arguments, so I coerce them to scalar values when I pass them.

The other way I could have handled this was to coerce my Sequence to a List on assignment like so.

my $x = Numpl.linspace( 0, 1, 500 ).List;

Then I could have assigned it to a scalar variable and still be free to iterate over it as many times as I like. If you have no need for laziness from your `linspace`

function, you could modify it to return a reified List either coercing the return value as above, or by instructing the Sequence to be eagerly evaluated.

return ( $start, * + $step ... $end ).List # or return eager $start, * + $step ... $end

But I like to be able to control the laziness of my `linspace`

result from outside the function.

Whichever way you do it, the end result should look like this

The wrapper module is working out well... a little too well. I wanted to tackle something a little more complex, and scrolling through the matplotlib gallery I saw it. It's high time for a histogram.

To be continued...