The 2024 Raku Advent Posts

(in chronological order, with comment references)

Day 25 – Raku 2024 Review

How time flies. Yet another year has flown by. 2024 was a year of changes, continuations and preparations. Let’s start with the changes:

Edument

Edument Central Europe, the branch of Edument that is based in Prague (and led by Jonathan Worthington), decided to stop (commercial) development of its Raku related products: Comma (the IDE for the Raku Programming Language) and Cro (a set of libraries for building reactive distributed systems).

Comma

The announcement:

With the discrepancy between revenue and development cost to continue being so large, and the prevailing economic environment forcing us to focus on business activities that at least pay for themselves, we’ve made the sad decision to discontinue development of the Comma IDE.

Fortunately, Jonathan Worthington was not only able to release the final commercial version as a free download, but was also able to release all of the sources of Comma. This would allow other people to continue Comma development.

With John Haltiwanger being the now de-facto project leader, this has resulted in a beta of the open source version of a Raku plugin for IntelliJ IDEA, as described on Day 20.

Cro

The announcement:

When Edument employees were by far the dominant Cro contributors, it made sense for us to carry the overall project leadership. However, the current situation is that members of the Raku community contribute more. We don’t see this balance changing in the near future.

With that in mind, we entered into discussions with the Raku Steering Council, in order that we can smoothly transfer control of Cro and its related projects to the Raku community. In the coming weeks, we will transfer the GitHub organization and release permissions to steering council representatives, and will work with the Raku community infrastructure team with regards to the project website.

As the source code of Cro had always been open source, this was more a question of handing over responsibilities. Fortunately the Raku Community reacted: Patrick Böker has taken care of making Cro a true open source project related to Raku, and the associated web site https://cro.raku.org is now being hosted on the Raku infrastructure. With many kudos to the Raku Infra Team!

A huge thanks

Sadly, Jonathan Worthington also indicated that they would only remain minimally involved in the further development of MoarVMNQP and Rakudo in the foreseeable future. As such, (almost) all of their modules were moved to the Raku Community Modules Adoption Center, where they were updated and re-released.

It’s hard to overstate the importance of Jonathan Worthington‘s work in the development and implementation of the Raku Programming Language. So on behalf of the current, past and future Raku Community members: Thank You!

Issues cleanup

At the beginning of October, Elizabeth Mattijsen decided to take on the large number of open Rakudo issues at that time: 1300+. This resulted in the closing of more than 500 issues: some just needed closing, some needed tests, and some could be fixed pretty easily.

They reported on that work in the Raku Fall Issue Cleanup blog post. Of the about 800 open issues remaining, almost 300 were marked as “fixed in RakuAST”, and about 100 were marked as “Will be addressed in RakuAST”. Which still leaves about 400 open because of other reasons, so there’s still plenty of work to be done here.

More cleanup: p6c

The original Raku ecosystem (“p6c”) is in the process of being completely removed. Since March 2023, the ecosystem was no longer being refreshed by zef. But it was still being refreshed by the Raku Ecosystem Archiver. But this stopped in August 2024, meaning that any updates of modules in that ecosystem would go unnoticed from then on.

At that time, every author who still had at least one module in the “p6c” ecosystem was given notice by creating an issue in the repository of the first of their modules in the META.list. Luckily many authors responded, either by indicating that they would migrate their module(s) to the “zef” ecosystem, or that they were no longer interested in maintaining.

Since then, most of the modules of the authors that responded, have been migrated. And work has started on the modules of the authors that did not respond. With the following results: at the beginning of 2024, there were still 658 modules in the “p6c” ecosystem (now 427, 35% less), by 230 different authors (now 138, 40% less).

Ecosystem statistics

In 2024, 558 Raku modules have been updated (or first released): up from 332 in 2023 (an increase of 68%). There are now 2304 different modules installable by zef by just mentioning their name. And there are now 12181 different versions of Raku modules available from the Raku Ecosystem Archive, up from 10754 in 2023, which means almost 4 module updates / day in 2024.

Rakudo

Rakudo saw about 2000 commits (MoarVM, NQP, Rakudo, doc) this year, which is about the same as in 2023. About one third of these commits were in the development of RakuAST (down from 75% in 2023).

Under the hood, behind the scenes

A lot of work was done under the hood of the various subsystems of Rakudo. So was the dispatcher logic simplified by introducing several nqp:: shortcuts, which made the dispatcher code a lot more readable and maintainable.

The Meta-classes of NQP and Raku also received a bit of a makeover, as most of them hadn’t been touched since the 2015 release: this resulted in better documentation, and some minor performance improvements. Support for TWEAK methods and a rudimentary dd functionality were also added to NQP.

The JVM backend also got some TLC in 2024: one under the hood change (by Daniel Green) made execution of Raku code on the JVM backend twice as fast!

Timo Paulssen made the interface with low-level debuggers such as gdb and lldb a lot less cumbersome on MoarVM, which makes adding / fixing MoarVM features a lot easier!

On the MoarVM backend the expression JIT (active on Intel hardware) was disabled by default: it was found to be too unreliable and did not provide any execution speed gains. This change made Rakudo on Intel hardware up to 5% faster overall.

Also on the MoarVM backend, Daniel Green completed the work on optimizing short strings started by Timo Paulssen and Bart Wiegmans, resulting in about a 2% speed improvement of the compilation of Raku code.

Work on the Remote Debugger (which so far had only been really used as part of Comma) has resumed, now with a much better command line interface. And can now be checked in the language itself with the new VM.remote-debugging method.

Some race conditions were fixed: a particularly nasty one on the lazy deserialization of bytecode that was very hard to reproduce, as well as some infiniloops.

A lot of work was done on making the Continuous Integration testing produce fewer (and recently hardly any) false positives anymore. Which makes life for core developers a lot easier!

New traits

Two new Routine traits were added to the Raku Programming Language in 2024.

is item

The is item trait can be used on @ and % sigilled parameters to indicate that a Positional (in the @ case) or an Associative (in the % case) is only acceptable in dispatch if it is presented as an item. It only serves as a tie-breaker, so there should always also be a dispatch candidate that would accept the argument when it is not itemized. Perhaps an example makes this more clear:

multi sub foo(@a)         { say "array" }
multi sub foo(@a is item) { say "item"  }
foo  [1,2,3];  # array
foo $[1,2,3];  # item

is revision-gated(“v6.x”)

The is revision-gated trait fulfils a significant part of the promise of the Raku Programming Language to be a 100-year programming language. It allows a developer to add / keep behaviour of a dispatch to a subroutine or method depending on the language level from which it is being called.

As with is item, this is implemented as a tie-breaker to be checked only if there are multiple candidates in dispatch that match a given set of arguments.

This will allow core and module developers to provide forward compatibility, as well as backward compatibility in their code (as long as the core supports a given language level, of course).

In its current implementation, the trait must be specified on the proto to allow it to work (this may change in the future), and it should specify the lowest language level it should support. An example of a module “FOO” that exports a “foo” subroutine:

unit module FOO;
proto sub foo(|) is revision-gated("v6.c") is export {*}
multi sub foo() is revision-gated("6.c") {
    say "6.c";
}
multi sub foo() is revision-gated("6.d") {
    say "6.d"
}

Then we have a program that uses the “FOO” module and calls the “foo” subroutine. This shows “6.d” because the current default language level is “6.d”.

use FOO;

foo();  # 6.d

However, if this program would like to use language level 6.c semantics, it can indicate so by adding a use v6.c at the start of the program. And get a different result in an otherwise identical program:

use v6.c;
use FOO;

foo();  # 6.c

John Haltiwanger has written an extensive blog post about the background, implementation and usage of the is revision-gated trait on Day 16, if you’d like to know more about it.

Language changes (6.d)

These are some of the more notable changes in language level 6.d: all of them add functionality, so are completely backward compatible.

Flattening

The .flat method optionally takes a :hammer named argument, which will deeply flatten any data structure given:

my @a = 1, [2, [3,4]];
say @a.flat;           # (1 [2 [3 4]])
say @a.flat(:hammer);  # (1 2 3 4)

One can now also use HyperWhatever (aka **) in a postcircumfix [ ] for the same semantics:

my @a = 1, [2, [3,4]];
say @a[*];   # (1 [2 [3 4]])
say @a[**];  # (1 2 3 4)

Min/max/minmax options

The .min / .max / .minmax methods now also accept the :by named argument to make it consistent with the sub versions, which should prevent unexpected breakage when refactoring code from a sub form to a method form (as the :by would previously be silently ignored in the method form).

say min <5 45 345>, :by(*.Str);  # 345
say <5 45 345>.min(*.Str);       # 345
say <5 45 345>.min(:by(*.Str));  # 345  ADDED

.are(Type)

The .are method now also accepts a type argument. If called in such a manner, it will return True if all members of the invocant matched the given type, and False if not. Apart from allowing better readable code, it also allows shortcutting if any of the members of the invocant did not match the given type. An example:

unless @args.are(Pair) {
    die "All arguments should be Pairs";
}

Language changes (6.e.PREVIEW)

The most notable additions to the future language level of the Raku Programming Language:

.nomark

The .nomark method on Cool objects returns a string with the base characters of any composed characters, effectively removing any accents and such:

use v6.e.PREVIEW;
say "élève".nomark;  # eleve

:smartcase

The .contains / .starts-with / .ends-with / .index / .rindex / .substr-eq methods now all accept a :smartcase named argument: a conditional :ignorecase. If specified with a True value, it will look at the needle to see if it is all lowercase. If it is, then :ignorecase semantics will be assumed. If there are any uppercase characters in the needle, then normal semantics will be assumed:

use v6.e.PREVIEW;
say "Frobnicate".contains("frob");              # False
say "Frobnicate".contains("frob", :smartcase);  # True
say "Frobnicate".contains("FROB", :smartcase);  # False

IO::Path.stem

The .stem method on IO::Path objects returns the .basename of the object without any extensions, or with the given number of extensions removed:

use v6.e.PREVIEW;
say "foo/bar/baz.tar.gz".IO.basename;  # baz.tar.gz
say "foo/bar/baz.tar.gz".IO.stem;      # baz
say "foo/bar/baz.tar.gz".IO.stem(1);   # baz.tar

RakuAST

About one third of this year’s work was done on the RakuAST (Raku Abstract Syntax Tree) project. It basically consists of 3 sub-projects, that are heavily intertwined:

  1. Development of RakuAST classes that can be used to represent all aspects of Raku code in an object-oriented fashion.
  2. Development of a grammar and an actions class to parse Raku source code and turn that into a tree of instantiated RakuAST objects.
  3. Development of new features / bug fixing in the Raku Programming Language and everything else that has become a lot easier with RakuAST.

RakuAST classes

There is little more to say about the development of RakuAST classes other than that there were 440 of them at the start of the year, and 454 of them at the end of the year. As the development of these classes is still very much in flux, they are not documented yet (other than in the test-files in the /t/12rakuast directory).

On the other hand, the RakuAST::Doc classes are documented because they have a more or less stable API to allow for the development of RakuDoc Version 2.

Raku Grammar / Actions

The work on the Raku Grammar and Actions has been mostly about implementing already existing features. This is measured by the number of Rakudo (make test) and roast (make spectest) test-files that completely pass with the new Raku Grammar and Actions. And these are the changes:

  • make test: 110/151 (72.8%) 140/156 (89.7%)
  • make spectest: 980/1356 (72.3%) 1155 / 1359 (85%)

A lot of work was done by Stefan Seifert, picking up the compile-time handling refactor that Jonathan Worthington had started in 2023, but was unable to bring to fruition. TPRF continued the funding for this work.

By the way, DuckDuckGo donated US$ 25000 to the foundation to allow this type of development funding to go on! Hint, hint!

Like last year, there are still quite a few features left to implement. Although it must be said that many tests are hinging on the implementation of a single feature, and often cause an “avalanche” of additional test-files passing when it gets implemented.

If you’d like to try out the new Raku Grammar and Actions, you should set the RAKUDO_RAKUAST environment variable to 1. The .legacy method on the Raku class will tell you whether the legacy (older) grammar is being used or not:

$ raku -e 'say Raku.legacy'
True
$ RAKUDO_RAKUAST=1 raku -e 'say Raku.legacy'
False

Localization

A cousin language was created: Draig, allowing you to write Raku code in Welsh!

Syntax Highlighting

The RakuAST::Deparse::Highlight module allows customizable Raku syntax highlighting of Raku code that is syntactically correct. It is definitely not usable for real-time syntax highlighting as it a. requires valid Raku code, and b. it may execute Raku code in BEGIN blocks, constant specifications and use statements.

A more lenient alternative is the Rainbow module by Patrick Böker.

RakuDoc

The final version of the RakuDoc v2.0 specification can now be completely rendered thanks to the tireless work of Richard Hainsworth and extensive feedback and proofreading by Damian Conway, as shown in Day 1 and Day 14.

Conference / Core Summit

Sadly it has turned out to be impossible to organize a Raku Conference (neither in-person or online), nor was it possible to organize a Raku Core Summit. We’re hoping for a better situation in 2025!

Beginner Videos

Completely out of the blue, a Dr Raku created about 90 Raku beginner videos on YouTube.

Summary

Looking back, again an amazing amount of work has been done in 2024!

Hopefully you will all be able to enjoy the Holiday Season with sufficient R&R, especially Kane Valentine (aka kawaii).

The next Raku Advent Blog is only 340 days away!

Day 24 – In Search of the Essence of Raku

Amongst all of its features, what do we consider to be the essence of Raku? In this advent post we will explore what makes Raku an exciting programming language and see if we can describe its essence.

Paul Graham wrote an essay about the 100 year language, back in 2003. In it he had this to say:

Who will design the languages of the future? One of the most exciting trends in the last ten years has been the rise of open-source languages like Perl, Python, and Ruby. Language design is being taken over by hackers. The results so far are messy, but encouraging. There are some stunningly novel ideas in Perl, for example. Many are stunningly bad, but that’s always true of ambitious efforts. At its current rate of mutation, God knows what Perl might evolve into in a hundred years.

Larry Wall took Paul Graham’s essay and ran with it:

“It has to do with whether it can be extensible, whether it can evolve over time gracefully.”

Larry referred to the expressiveness of human languages as his inspiration for Raku:

  • Languages evolve over time. (“It’s okay to have dialects…”)
  • No arbitrary limits. (“And they naturally cover multiple paradigms”)
  • External influences on style.
  • Fractal dimensionality.
  • Easy things should be easy, hard things should be possible.
  • “And, you know, if you get really good at it, you can even speak CompSci.”

Naoum Hankache’s Raku Guide introduces Raku:

Raku is a high-level, general-purpose, gradually typed language. Raku is multi-paradigmatic. It supports Procedural, Object Oriented, and Functional programming.

The feature list on Raku.org goes further:

  • Object-oriented programming including generics, roles and multiple dispatch
  • Functional programming primitives, lazy and eager list evaluation, junctions, autothreading and hyperoperators (vector operators)
  • Parallelism, concurrency, and asynchrony including multi-core support
  • Definable grammars for pattern matching and generalized string processing
  • Optional and gradual typing

In Search of the Essence

As we can see, Raku has many rich features. Maybe the essence of Raku lies somewhere among them.

Deep Unicode

It’s the 21st Century and programming languages are finally waking up to the fact that we live in a multi-lingual world. Raku is a Unicode centric language in a way that no other language manages.

Rich Concurrency

Again programming languages are only beginning to catch up with the reality that every computer has multiple cores. Sometimes very many cores. Raku excels, alongside other modern languages, at delivering the tools required for effective concurrent and asynchronous programs.

Multi Paradigm

Raku is a richly functional language that combines the procedural and object oriented encapsulation constructs to span paradigms.

A Cornucopia of Language Features

Multi Language with Slangs and Inlines

  • Many languages in the same execution unit. No need to be an async/ IPC boundary away.
  • Representational polymorphism

The breadth of the Raku language is indeed impressive. But I don’t think it’s instructive, or even possible to try and distill an essence from such a diverse list of capabilities.

What then, is the Essence of Raku?

Perl and Raku both adhere to the famous motto:

TMTOWTDI (Pronounced Tim Toady): There is more than one way to do it.

This is indeed demonstrably true as shown by Damian Conway whenever he blogs about or talks about Raku. Indeed in Why I love Raku he sums up with this endorsement:

More than any other language I know, Raku lets you write code in precisely the way that suits you best, at whatever happens to be your (team’s) current level of coding sophistication, and in whichever style you will later find most readable …and therefore easiest to maintain.

How do you identify the essence of a deliberately broad multi-paradigm language? Perhaps the essence is not so much about some aspect of the core language. Perhaps it is much more about the freedom it gives you to choose, instead of the language choosing for you.

Like Raku, the community is not opinionated. If you choose a particular approach, the community is there to help you. If you want to learn what’s possible, the community will offer up multiple approaches, as shown by many Stack Overflow answers.

Which leaves me with this thought:

The essence of Raku is the freedom to choose for yourself. And the freedom to choose differently tomorrow.

Day 23 – Santa’s Print Shop

A lead-free area

Elf Nebecaneezer (‘Neb’) was welcoming some young new workers to his domain and doing what old folks like to do: pontificate. (His grandchildren politely, but behind his back, call it “bloviating.”)

“In the old days, we used hand-set lead type, then gradually used ever-more-modern methods; now we prepare the content in PDF and send a copy to another company to print the content on real newsprint. It still takes a lot of paper, but much less ink and labor (as well as being safer1).”

“Most young people are very familiar with HTML and online documents, but, unless your browser and printer are very fancy, it’s not easy to create a paper copy of the thing you need to file so that it’s very usable. One other advantage of hard-copy products is that they can be used without the internet or electricity. Remember, the US had a hugely-bad day recently when a major internet service provider had a problem!” [He continued to speak…]

Inspiration and perspiration

Now let’s get down to ‘brass tacks.’ You all are supposed to be competent Raku users, so I will show you some interesting things you can do with PDF modules. But first, a little background on PDF (Adobe’s Portable Document Format).

The PDF was developed by the Adobe company in 1991 to replace the PostScript (PS) format. In fact, The original PS code is at the heart of PDF. Until 2008, Adobe defined and continued to develop PDF. Beginning in 2008, the ISO defined it in its 32000 standard. That standard is about a thousand pages long and can be obtained online at https://www.pdfa-inc.org at no cost.

One other important piece of the mix is the CLI ghostscript interpreter which can be used, among other things, to compress PDF documents.

Before we continue, remember, we are a Debian shop, and any specific system software I mention is available in current versions.

Document creation

We are still improving Rakudoc (see the recent Raku Advent post by Richard Hainsworth), but it can be used now for general text to produce PDF output. However, for almost any specific printed product needed, we can create the PDF on either a one-time case or a specific design for reuse.

A good example of that is the module PDF::NameTags which can be modified to use different layouts, colors, fonts, images, and so forth. Its unique output feature is its reversibility–when printed two-sided, the person’s name shows regardless of whether the badge flips around or not. That module was used to create the name tags we are are wearing on our lanyards, and I created that module! I’ll be using parts of that module as an example as I continue.

Actually, I have a GitHub account under an alias, ‘tbrowder’, and I have published several useful modules to help my PDF work. I’ll mention some later.

Note I always publish my finished modules via ‘zef’, the standard Raku package manager. Then they are easily available for installation, and they automatically get listed on https://Raku.land and can easily be found. Why do I do that, especially since no one else may care? (As ‘@raiph’ or someone other Raku user once said, modules are usually created for the author’s use.) Because, if it helps someone else, as the Golden Rule says, it may be the right thing to do (as I believe Font::Utils does).

Fonts

Fonts used to be very expensive and hard to set type with, so the modern binary font is a huge improvement! Binary fonts come in different formats, and their history is fascinating.

This shop prefers OpenType fonts for two practical reasons: (1) they provide extensive Unicode glyph coverage for multi-language use (we operate world-wide as you well know) and (2) they provide kerning for more attractive type setting.

By using the HarffBuzz library, the final PDF will only contain the glyphs actually used by the text (the process is called ‘subsetting’). If a user is not running Debian 12 or later, then he or she can additionally ‘use’ module Compress::PDF which provides a subroutine which uses Ghostscript to remove unused glyphs. The module also provides a CLI program, pdf-compress, which does the same thing.

For Latin and most European languages we use the following font collections available as Debian packages:

FontDebian package
GNU Free Fontsfonts-freefont-otf
URW Base 35,fonts-urw-base35
E B Garamondfonts-egaramond, fonts-garamond-extra
Cantarellfonts-cantarell

For other languages we rely on the vast glyph coverage of Google’s Noto fonts (in TrueType format). Debian has many of those fonts, but we find it easier to find the needed font at Google and download them onto our server as zip archives. After unzipping, we move the desired files to ‘/usr/local/share/fonts/noto’. We currently have these 10 Noto fonts available (plus 175 other variants):

Font
NotoSerif-Regular.ttf
NotoSerif-Bold.ttf
NotoSerif-Italic.ttf
NotoSerif-BoldItalic.ttf
NotoSans-Regular.ttf
NotoSans-Bold.ttf
NotoSans-Italic.ttf
NotoSans-BoldItalic.ttf
NotoSansMono-Regular.ttf
NotoSansMono-Bold.ttf

Note the file names above are in the family and style order as the Free Fonts in our own $HOME directory.

To get a feel for the span of glyphs in some of the fonts, the FreeSerif font has over 1,000 glyphs and can be used for many languages. We typically use that font, and the rest of that font family, for most of our work around Europe and the Americas. For the rest of the world, Google’s Noto fonts should cover all but a tiny fraction of the population.

One of the many strengths of Raku is its handling of Unicode as a core entity. You can read about Unicode at its website. Of particular interest are the code charts at https://www.unicode.org/charts. If you select any chart you will see that the code points are shown in hexadecimal. In Raku the code points are natively in decimal. Fortunately, Raku has a method to do that:

# convert decimal 178 to hexadecimal (base 16)
say 178.base(16); # OUTPUT: B2
# convert a hexadecimal 'A23' to decimal
say 'A23'.parse-base(16); # OUTPUT: 2595

Or we can look at Wikipedia where there are charts showing both hexidecimal and decimal code points (Unicode_chars).

Font files and utilities

Not released yet is my Raku module PDF::FontCollection which encapsulates useful font collections into a single reference list. The module has routines allowing the user to get a loaded font with a short code mnemonically associated with the font collection (a digit, and a one- or two-letter character for its style). It has an installed binary to show its fonts by number, code, and name. However, my most useful module, Font::Utils, has made this module effectively obsolete.

I now introduce my almost-released module Font::Utils which uses fonts already installed and collects them into a file called font-files.list which is then placed into the user’s $HOME/.Font-Utils directory.

Since our servers already have the desired OpenType fonts installed, using Font::Utils is actually more convenient since you can arrange the font list any way you want including: (1) creating your own mnemonic keys for easy reference, (2) deleting or adding data lines, and (3) reordering data lines.

Note that the actual OpenType font files are quite large, but a good design will ensure they are not loaded until specifically called for in the program. If they are already loaded, calling the routine will be is a no-op. The Font::Utils module has one such routine.

Other non-Latin languages are covered in many freely available font collections, including right-to-left and other orientations along with layouts for users who need that capability (the Noto fonts are a good example). As noted, those can be easily added to your Font::Utils collection.

Let’s take a look at the first couple of lines in the default installation of my $HOME/.Font-Utils/font-files.list:

# key  basename   path
 1  FreeSerif.otf  /usr/share/fonts/opentype/freefont/FreeSerif.otf

We see the comments and the first line of data consisting of three fields. The first field is the unique code which you may change. The second field is the font file’s basename, and the last field is file font file’s path. You may delete or reorder or add new font entries as you wish.

Now let’s say you want to publish some text in Japanese. Oh, you say you don’t know Japanese? And you don’t have a keyboard to enter the characters? No problem!, There is a way to do that.

We first find from Wikipedia that some Unicode characters for the Japanese language are in the Hiragan collection, which covers hexadecimal code points 3041 through 3096 and 3099 through 309F. Then we create a space-separated string of the characters for each word. We’ll use an arbitrary list of them:

my $jword1 = "3059 306A 306B 306C 305D";
my $jword2 = "3059-305D"; # same as $jword1 but with a hyphen for a range
my $jword3 = "306B-306F";

Note that we can use a hyphen to indicate a contiguous range of code points. (We could also use decimal code points, but that’s a bit more awkward due to ease and larger number of characters required as well as the confusing use of the joining ‘-‘ with subtraction.)

Oops, what font shall we use? I couldn’t find a suitable font with the searches on Debian, so I went online to Google, searched for Hiragana, and found the font with description Noto Serif Japanese.

I selected it, downloaded it, and got file Noto_Serif_JP.zip. I created a directory named google-fonts and moved the zip file there where I then unpacked them to get directory Noto_Serif_JP with files:

README.txt
NotoSerifJP-VariableFont_wght.ttf
OFL.txt
static
static/NotoSerifJP-Bold.ttf
static/NotoSerifJP-SemiBold.ttf
static/NotoSerifJP-Medium.ttf
static/NotoSerifJP-Light.ttf
static/NotoSerifJP-ExtraLight.ttf
static/NotoSerifJP-ExtraBold.ttf
static/NotoSerifJP-Black.ttf
static/NotoSerifJP-Regular.ttf

The main font is a variable one, so I tried it to see the results.

Text

There are many ways to lay out text on a page. The most useful for general use is to create reusable text boxes.

Reusable text boxes

# define it
my PDF::Content::Text::Box $tb .= new(
   :$text, :$font, :$font-size, :$height,
   # style it
   :WordSpacing(5), # extra spacing for Eastern languages
);
#...clone it and modify it...
$tb.clone(:content-width(200));

Use the $page.text context to print a box:

my @bbox;
$page.text: {
    .text-position = $x, $y;
    # print the box and collect the resulting bounding box coordinates
    @bbox = .print: $tb;

Graphics and clipping

I won’t go into it very much, but you can do almost anything with PDF. The aforementioned PDF::NameTags module has many routines for drawing and clipping. Another of my modules on deck is PDF::GraphicsUtils which will encompass many similar routines as well as the published PDF::Document module.

Tricks and hints for module authors

Create a run alias

I was having trouble with the variable syntax needed testing scripts as well as test modules. Raku user and author @librasteve suggested creating an alias to do that. The result from my .bash_aliases file;

alias r='raku -I.'      # adhoc script Raku run command
alias rl='raku -I./lib' # adhoc script Raku run command

Use a load test

I was having problems with modules once and @ugexe suggested always using a load test for checking all modules will compile. Now I always create a test script similar to this:

# file: t/0-load-test.t
use Test;
my @modules = <
  Font::Utils
  Font::Utils::FaceFreeType
  Font::Utils::Misc
  Font::Utils::Subs
>;

plan +@modules;

for @modules {
    use-ok $_, "Module $_ can be used okay";
}

And run it like this: r t/0*t

$ r t/0*t
# OUTPUT:
1..4
ok 1 - Module Font::Utils can be used okay
ok 2 - Module Font::Utils::FaceFreeType can be used okay
ok 3 - Module Font::Utils::Misc can be used okay
ok 4 - Module Font::Utils::Subs can be used okay

=finish

Use =finish to debug a LONG rakumod file. I use it when I’m in the process of adding a new sub or modifying one and commit some error that causes a panic failure without a clear message. I add the =finish after the first routine and see if it compiles. If it does I move the =finish line to follow the next sub, and so on.

When I do get a failure, I have a better idea of where the bad code is. Note I do sometimes have to reorder routines becaause of inter-module dependencies. It’s also a good time to move completely independent routines to a lower-level module as described next.

Encapsulate small, independent routines

Sometimes, as in Font::Utils, I create a single, long file with routines that are dependent on other routines so that it is difficult to tell what is dependent upon what. Then I start creating another, lower-level module that has routines that are non-dependent on higher level modules. You can see that structure in the load test output shown above.

Use the BEGIN phaser

Module authors often need access to the user’s $HOME directory, so use of the BEGIN phaser as a block can make it easier to access it from the Build module, as well as the base modules. Here is that code from the Font::Utils module:

unit module Font::Utils;

use PDF::Font::Loader :load-font;
our %loaded-fonts is export;
our $HOME is export = 0;
our $user-font-list is export;
our %user-fonts     is export;
BEGIN {
    if %*ENV<HOME>:exists {
        $HOME = %*ENV<HOME>;
    }
    else {
        die "FATAL: The environment variable HOME is not defined";
    }
    if not $HOME.IO.d {
        die qq:to/HERE/;
        FATAL: \$HOME directory '$HOME' is not usable.
        HERE
    }
    my $fdir = "$HOME/.Font-Utils";
    mkdir $fdir;
    $user-font-list = "$fdir/font-files.list";
}
INIT {
    if not $user-font-list.IO.r {
        create-user-font-list-file;
    }
    create-user-fonts-hash $user-font-list;
    create-ignored-dec-codepoints-list;
}

That BEGIN block creates globally accessible variables and enables easy access for any build script to either create a new user fonts list or check an existing one. The INIT block then uses a routine to create the handy %user-fonts hash after the Raku compilation stage enables it.

As @lizmat and others on IRC #raku always warn about global variables: danger lurks from possible threaded use. But our ‘use cases’ should not trigger such a problem. However, other routines may, and we have used a fancy module to help the problem: OO::Monitors by @jnthn. See it used to good effect in the class (monitor) Font::Utils::FaceFreeType.

Experiment: Embedded links

Currently the PDF standard doesn’t deal with active links, but my Raku friend David Warring gave me a solution in an email. I think I’ve tried it, and I think it works, but YMMV.

David said “Hi Tom, Adding a link can be done. It’s a bit lower level than I’d like and you need to know what to look for. Also needs PDF::API6, rather than PDF::Lite. I’m looking at the pdfmark documentation. It’s a postscript operator and part of the Adobe SDK. It’s not understood directly by the PDF standard, but it’s just a set of data-structures, so a module could be written for it. I’ll keep looking.”

His code:

# Example:
use PDF::API6;
use PDF::Content::Color :&color, :ColorName;
use PDF::Annot::Link;
use PDF::Page;

my PDF::API6 $pdf .= new;
my PDF::Page $page = $pdf.add-page;

$page.graphics: {
    .FillColor = color Blue;
    .text: {
        .text-position = 377, 515;
        my @Border = 0,0,0; # disable border display
        my $uri = 'https://raku.org';
        my PDF::Action::URI $action = $pdf.action: :$uri;
        my PDF::Annot::Link $link = $pdf.annotation(
            :$page,
            :$action,      # action to follow the link
            :text("Raku homepage"), # display text (optional)
            :@Border,
         );
    }
}
$pdf.save-as: "link.pdf";

Use modules App::Mi6 and Mi6::Helper

Because of the artistic nature of our work, we often need to create our own modules for new products. In that event, we use module App::Mi6 and its binary mi6 to ease the labor of managing recurring tasks with module development. By using a logical structure and a dist.ini configuration file, the user can create Markdown files for display on GitHub or GitLab, test his or her test files in directories t and xt, and publish the module on ‘Zef/Fez’ with one command each.

By using my module Mi6::Helper, the author can almost instantly create a new module ‘git’ source repository with its structure ready to use app mi6 with much boiler plate already complete.

I’m also working on a dlint program to detect structural problems in the module’s git repository. It is a linter to check the module repo for the following:

  1. Ensure file paths listed in the META6.json file match those in the module’s /resources directory.
  2. Ensure all use X statements have matching depends entries in the META6.json file.
  3. Ensure all internal sub module names are correct.

The linter will not correct these problems, but if there is interest, that may be added in a future release.

Party time: finale

Neb concluded his presentation:

“Bottom line: Raku’s many PDF modules provide fairly easy routines to define any pre-press content needed. Those modules continue to be developed in order to improve ease of use and efficiency. Graphic arts have always been fun to me, but now they are even ‘funner!'”.

Santa’s Epilogue

As I always end these jottings, in the words of Charles Dickens’ Tiny Tim, “may God bless Us, Every one!A Christmas Carol, a short story by Charles Dickens (1812-1870), a well-known and popular Victorian author whose many works include The Pickwick PapersOliver TwistDavid CopperfieldBleak HouseGreat Expectations, and A Tale of Two Cities2.

Footnotes

  1. Old print shops: When I was in the seventh grade in my mother’s home town of McIntyre, Georgia, where we lived while my Dad was in Japan, we had a field trip to the local print shop of the Wilkinson County News. There the printer had to set lead type by hand, install the heavy frame set of type for the full page on the small, rotary press. Then he hand fed fresh newsprint paper as the apparatus inked the type-frame and then pressed the paper against the inked type. Finally, he removed the inked sheet and replaced it with the next copy. One of the kids noted the sharp pin on the press plate to hold the paper and asked if that ever caused problems. The printer showed us his thumb with a big scar and said that was the mark of an old time press man and just an expected hazard of the trade. ↩︎
  2. A Christmas Carol, a short story by Charles Dickens (1812-1870), a well-known and popular Victorian author whose many works include The Pickwick PapersOliver TwistDavid CopperfieldBleak HouseGreat Expectations, and A Tale of Two Cities. ↩︎

Day 22 – Wrapping a Christmas Present

A few months back

In the smoke-filled (virtual) room of the council of the high (from the smoke) elves, the wizened textualist Geoff, said “All of my stuff is in boxes and containers.” Empty shelves behind him indicated he was moving house.

“When you have a complex module,” Geoff continued, “and its difficult to describe how to install it, do all the steps in a container, and show the Dockerfile.”

“Aha!” said the newest member, who drew his vorpal sword, and went forth to slay the Jabberwock, aka putting RakuAST::RakuDoc::Render into a container.

“Beware the Jabberwock, my son!
The jaws that bite, the claws that catch!”

After days of wandering the murky jungle of Docker/Alpine/Github/Raku documentation, the baffled elf wondered if he was in another fantasy:

“So many rabbit holes to fall down.”

Rabbit Hole, the First – alpinists beware

Best practice for a container is to chose an appropriate base image. Well obviously, there’s the latest Raku version by the friendly, hard-working gnomes at Rakuland. So, here’s the first attempt at a Dockerfile:

FROM docker.io/rakuland/raku
# Copy in Raku source code and build
RUN mkdir -p /opt/rakuast-rakudoc-render
COPY . /opt/rakuast-rakudoc-render
WORKDIR /opt/rakuast-rakudoc-render
RUN zef install . -/precompile-install

It failed. After peering at the failure message, it seemed that at least one of the dependent modules used by the rakuast-rakudoc-render distribution needs a version of make.

That’s easily fixed, just add in build-essentials, the vorpal-sworded elf thought. Something like:

FROM docker.io/rakuland/raku

# Install make, gcc, etc.
RUN apt-get update -y && \
    apt-get install -y build-essential && \
    apt-get purge -y

# Copy in Raku source code and build
RUN mkdir -p /opt/rakuast-rakudoc-render
COPY . /opt/rakuast-rakudoc-render
WORKDIR /opt/rakuast-rakudoc-render
RUN zef install . -/precompile-install

Failure! No apt.

“How can there not be APT??” the Ubuntu-using elf thought in shock. Turns out that the rakuland/raku image is built on an Alpine base, and Alpine have their own package manager apk.

Unfortunately, build-essential is a debian package, but at the bottom of this rabbit hole lurks an apk equivalent package build-base, leading to:

FROM docker.io/rakuland/raku

# Install make, gcc, etc.
RUN apk add build-base

# Copy in Raku source code and build
RUN mkdir -p /opt/rakuast-rakudoc-render
COPY . /opt/rakuast-rakudoc-render
WORKDIR /opt/rakuast-rakudoc-render
RUN zef install . -/precompile-install

Lo! upon using the Podman desktop to build an image from the Dockerfile, the process came to a succesful end.

But now to make things easier, there needs to be a link to the utility RenderDocs, which takes all the RakuDoc sources from docs/ and renders them to $*CWD (unless over-ridden by --src or --to, respectively). It will also render to Markdownunless an alternative format is given.

FROM docker.io/rakuland/raku

# Install make, gcc, etc.
RUN apk add build-base

# Copy in Raku source code and build
RUN mkdir -p /opt/rakuast-rakudoc-render
COPY . /opt/rakuast-rakudoc-render
WORKDIR /opt/rakuast-rakudoc-render
RUN zef install . -/precompile-install

# symlink executable to location on PATH
RUN ln -s /opt/rakuast-rakudoc-render/bin/RenderDocs /usr/local/bin/RenderDocs

# Directory where users will mount their documents
RUN mkdir /doc

# Directory where rendered files go
RUN mkdir /to
WORKDIR /

AND!!! when a container was created using this Dockerfile and run with its own terminal, the utility RenderDocs was visible. Running

RenderDocs -h

produced the expected output (listing all the possible arguments).

Since the entire distribution is included in the container, running

RenderDocs --src=/opt/rakuast-rakudoc-render/docs README

will render README.rakudoc in --src to /to/README.md because the default output format is MarkDown.

“Fab!”, screamed the boomer-generation newbie elf. “It worked”.

“Now lets try HTML”, he thought.

RenderDocs --format=HTML --src=/opt/rakuast-rakudoc-render/docs README

Failure: no sass.

expletive deleted“, he sighed. “The Jabberwok is not dead!”

There are two renderers for creating HTML. One produces a single file with minimal CSS so that a normal browser can load it as a file locally and it can be rendered without any internet connection. This renderer is triggered using the option --single. Which the containerised RenderDocs handles without problem.

Rabbit Hole, the second – architecture problems

But the normal use case is for HTML to be online, using a CSS framework and JS libraries from CDN sources. Since the renderer is more generic, it needs to handle custom CSS in the form of SCSS. This functionality is provided by calling an external program sass, which is missing in the container.

An internet search yields the following snippet for a container.

# install a SASS compiler
ARG DART_SASS_VERSION=1.82.0
ARG DART_SASS_TAR=dart-sass-${DART_SASS_VERSION}-linux-x64.tar.gz
ARG DART_SASS_URL=https://github.com/sass/dart-sass/releases/download/${DART_SASS_VERSION}/${DART_SASS_TAR}
ADD ${DART_SASS_URL} /opt/
RUN cd /opt/ && tar -xzf ${DART_SASS_TAR} && rm ${DART_SASS_TAR}
RUN ln -s /opt/dart-sass/sass /usr/local/bin/sass

The container image builds nicely, but the RenderDocs command STILL chokes with an unavailable sass.

Except that diving into the container’s murky depths with an ls /opt/dart-sass/ shows that sass exists!

The newbie was stumped.

So rested he by the Tumtum tree
And stood awhile in thought.

Turns out that the Alpine distribution uses a different compiler, and the wonderful dart-sass fae provide a suitable binary so a simple change was enough to get sass working in the container.

- ARG DART_SASS_TAR=dart-sass-${DART_SASS_VERSION}-linux-x64.tar.gz
+ ARG DART_SASS_TAR=dart-sass-${DART_SASS_VERSION}-linux-x64-musl.tar.gz

simple does not mean found at once, but the container contains RenderDocs, which produces markdown and HTML rendered files.

One, two! One, two! And through and through
The vorpal blade went snicker-snack!
He left it dead, and with its head
He went galumphing back.

“I can publish this image so everyone can use it,” the FOSS fanatic elf proclaimed.

So the docker container image can be accessed using a FROM or PULL using the URL

docker.io/finanalyst/rakuast-rakudoc-render

Rabbit Hole, the third – Versions

“And hast thou slain the Jabberwock?
Come to my arms, my beamish boy!
O frabjous day! Callooh! Callay!”

“It would be great,” mused the triumphant elf, “if RakuDoc sources, say for a README could be automatically included as the github README.md of repo”.

“May be as an action?”

Github actions can use containers to process files in a repo. Essentially, in an action, the contents of a repo are copied to a github-workspace, then they can be processed in the webspace, and changes to the workspace have to be committed and pushed back to the repository.

With a container, the contents of the workspace need to be made available to the container. Despite some documentation that starting a container in a github action automatically maps the github-workspace to some container directory, the exact syntax is not clear.

In order to discover how to deal with the multitude of possibilities, a new version of RenderDocs got written, and a new image generated, and again, and again … Unsurprisingly, between one meal and another, the ever hungry elf forgot which version was being tested.

“I’ll just include a --version argument,” thought the elf. “I’ll can ask the Super Orcs!”.

And there behold was an SO answer to a similar question, and it was written by no lesser a high elf than the zefish package manager ugexe, not to be confused with the other Saint Nick, the package deliverer.

Mindlessly copying the spell fragment into his CLI script as:

multi sub MAIN( :version($v)! = False {
    say "Using version {$?DISTRIBUTION.meta<version>} of rakuast-rakudoc-render distribution."
    if $v;
}

the elf thought all done! “Callooh! Callay!”.

Except RenderDocs -v generated Any.

“SSSSSSSSh-,” the elf saw the ominous shadow of Father Christmas looming, “-ine a light on me”.

On the IRC channel, the strong willed Coke pointed out that a script does not have a compile time variable, such as, $?DISTRIBUTION. Only a module does.

The all-knowing elf wizard @lizmat pointed out that command line scripts should be as short as possible, with the code in a module that exports a &MAIN.

Imbibing this wisdom, our protagonist copied the entire script contents of bin/RenderDocs to lib/RenderDocs.rakumod, added a proto sub MAIN(|) is export { {*} }, then made a short command line script with just use RenderDocs.

Inside the container terminal:

# RenderDocs -v
Using version 0.20.0 of rakuast-rakudoc-render distribution.

With that last magical idiom, our intrepid elf was ported from one rabbit hole back to the one he had just fallen down.

Rabbit Hole, the fourth – Actions

“Beware the Jubjub bird, and shun
The frumious Bandersnatch!”

“I seem to be going backwards,” our re-ported elf sighed.

Once again, the github documentation was read. After much study and heartbreak, our hero discovered a sequence that worked:

  1. Place Lavendar and sandalwood essential oils in a fragrance disperser
  2. Prepare a cup of pour over single-origin coffee with a spoon of honey, and cream
  3. In a github repo create a directory docs containing a file README.rakudoc
  4. In the same github repo create the structure
    .github/
        workflows/
            CreateDocs.yml
  1. Write the following content to CreateDocs.yml
    name: RakuDoc to MD
    on:
      # Runs on pushes targeting the main branch
      push:
        branches: ["main"]
      # Allows you to run this workflow manually from the
      # Actions tab workflow_dispatch:
    jobs:
        container-job:
            runs-on: ubuntu-latest
            steps:
                - name: Checkout code
                  uses: actions/checkout@master
                  with:
                    persist-credentials: false
                    fetch-depth: 0
                - name: Render docs/sources
                  uses: addnab/docker-run-action@v3
                  with:
                    image: finanalyst/rakuast-rakudoc-render:latest
                    registry: docker.io
                    options: -v ${{github.workspace}}/docs:/docs -v ${{github.workspace}}:/to
                    run: RenderDocs

After examining the github actions logs, it seemed the rendered files were created, but the repository was not changed.

“Perhaps I should have used milk and not cream …” thought our fantasy elf.

There is in fact a missing step, committing and pushing from the github-workspace back to the repository. This can be done by adding the following to CreateDocs.yml:

- name: Commit and Push changes
  uses: Andro999b/push@v1.3
  with:
    github_token: ${{ secrets.GITHUB_TOKEN }}
    branch: 'main'

Even this did not work! Github refused absolutely to write changes to the repository.

The weary elf substituted Lemon grass for Lavender in step 1, and just to be certain changed the repo settings following the instructions from the Github grimore 

  1. select Settings in the repo’s main page
  2. select Actions then General
  3. from the dropdown for GITHUB_TOKEN, select the one for read and write access.
  4. Save settings

The content – at this stage of the tale – of CreateDocs.yml is

name: RakuDoc to MD
on:
  # Runs on pushes targeting the main branch
  push:
    branches: ["main"]
  # Allows you to run this workflow manually from the Actions tab
  workflow_dispatch:
jobs:
    container-job:
        runs-on: ubuntu-latest
        steps:
            - name: Checkout code
              uses: actions/checkout@master
              with:
                persist-credentials: false
                fetch-depth: 0
            - name: Render docs/sources
              uses: addnab/docker-run-action@v3
              with:
                image: finanalyst/rakuast-rakudoc-render:latest
                registry: docker.io
                options: -v ${{github.workspace}}/docs:/docs -v ${{github.workspace}}:/to
                run: RenderDocs
            - name: Commit and Push changes
              uses: Andro999b/push@v1.3
              with:
                github_token: ${{ secrets.GITHUB_TOKEN }}
                branch: 'main'

It worked. “The Christmas present is now available for anyone who wants it”, thought our elf.

’Twas brillig, and the slithy toves
Did gyre and gimble in the wabe:
All mimsy were the borogoves,
And the mome raths outgrabe.
(Jabberwocky, By Lewis Carroll)

Remember to git pull for the rendered sources to appear locally as well.

Rabbit Hole, the fifth – Diagrams

“Wouldn’t it be nice to wrap the present in a ribbon? Why not put diagrams in the Markdown file? “

Our elf was on a streak, and fell down another rabbit hole: github does not allow svg in a Markdown file it renders from the repo. “It is impossible,” sighed the tired elf.

Alice laughed. “There’s no use trying,” she said: “one can’t believe impossible things.”
“I daresay you haven’t had much practice,” said the Queen. “When I was your age, I always did it for half-an-hour a day. Why, sometimes I’ve believed as many as six impossible things before breakfast.”
(Through the Looking Glass, Lewis Carroll)

Diagrams can be created using the dot program of Graphviz, which is a package that Alpine provides. So, we can create a custom block for RakuAST::RakuDoc::Render that takes a description of a graph, sends it to dot, gets an svg file back and inserts into the output.

Except: github will not allow svg directly in a markdown file for security reasons.

But: it will allow an svg in a file that is an asset on the repo. So, now all that is needed is to save the svg in a file, reference the file in the text, and copy the asset to the same directory as the Markdown text.

Except: the time stamps on the RakuDoc source files and the output files seem to be the same because of the multiple copying from the repo to the actions workspace to the docker container. So: add a --force parameter to RenderDocs.

So in Raku impossible things are just difficult.

The final content of CreateDocs.yml is now

name: RakuDoc to MD
on:
  # Runs on pushes targeting the main branch
  push:
    branches: ["main"]
  # Allows you to run this workflow manually from the Actions tab
  workflow_dispatch:
jobs:
    container-job:
        runs-on: ubuntu-latest
        steps:
            - name: Checkout code
              uses: actions/checkout@master
              with:
                persist-credentials: false
                fetch-depth: 0
            - name: Render docs/sources
              uses: addnab/docker-run-action@v3
              with:
                image: finanalyst/rakuast-rakudoc-render:latest
                registry: docker.io
                options: -v ${{github.workspace}}/docs:/docs -v ${{github.workspace}}:/to
                run: RenderDocs --src=/docs --to=/to --force
            - name: Commit and Push changes
              uses: Andro999b/push@v1.3
              with:
                github_token: ${{ secrets.GITHUB_TOKEN }}
                branch: 'main'

Try adding a graph to a docs/README.rakudoc in a repo, for instance:


=begin Graphviz :headlevel(2) :caption<Simple example>
    digraph G {
        main -> parse -> execute;
        main -> init;
        main -> cleanup;
        execute -> make_string;
        execute -> printf
        init -> make_string;
        main -> printf;
        execute -> compare;
    }
=end Graphviz

Now you will have a README with an automatic Table of Contents, all the possibilities of RakuDoc v2, and an index at the end (if you indexed any items using X<> markup).

(Sigh: All presents leave wrapping paper! A small file called semicolon_delimited_script is also pushed by github’s Commit and Push to the repo.)

Day 21 – Dam Mega Christmas

Source file: Le Grand PortageDerivative work: Rehman, CC BY 2.0 https://creativecommons.org/licenses/by/2.0, via Wikimedia Commons

[Edited by author]

Scientists have also discovered that so-called megastructures built by humans can also affect the Earth’s rotation. Take the 185m (about 600 feet) tall Three Gorges Dam. Spanning the Yangtze River in Hubei province, Central China, it is the largest dam in the world and is over 2,300m (7,500 feet) in length.

Its vital statistics are dizzying. It was made using 28 million cubic metres of concrete and enough steel to build 63 copies of the Eiffel Tower. It took 40,000 people 17 years to construct, at a total cost of $37 billion (£28 billion). The dam can hold 40 billion cubic metres of water – about 16 million Olympic-sized swimming pools.

Dam, thought Rudolph (for he was prone to silent cursing). He was worried that this may confuse Santa’s SaNtav (ed – that’s a stretch!).

He decided to flex his Raku skills there and then and to check this nonsense once and for all. Luckily with his friends Anton and Ab5stract, he had the full power of the IntelliJ JetBrains Raku Plugin (the plugin formerly known as Comma) and Jupyter::Chatbook and could fire up a Jupyter Raku notebook in a jiffy.

IntelliJ Jetbrains Comma Raku Plugin

He loved it when his hooves hit the keyboard and he was able to get all that cool IDE shit:

  • Jupyter Raku kernel alongside Python
  • Raku Syntax highlighting in notebook cells
  • Latex and all manner of Jupyter cell magics
  • Out of this world LLM cell magic

But Rudi wanted even more power – he wanted to calculate some awesome numbers and needed a tool to keep him straight in his Physics.

Physics::Measure

Wait, right there in a notebook he could use the raku Physics::Measure module…

zef Physics::Measure did the trick.

And here’s how it went…

sorry about the images and the spill to the right margin for legibility here – please go here to copy and paste into your own notebook … meantime it may help to embiggen your browser view

Dam Close Call

Phew thought Rudolph – if I can just dial in the differential of 1.45µsec to the SaNtav (groan!). But, then he noticed that his hooves were too big to dial in the new numbers…

~librasteve

with inspiration from here.

PS. the full text, docker instances, installation instructions and example code is here if you want to emulate Rudolph – https://github.com/librasteve/raku-Physics-Measure-Jupyter

PPS. As a stretch goal, a mince pie to the first comment below that shows how to make a custom Physics::Measure unit called the mega-pool.

Day 20 – Re-introducing a Raku plugin for IntelliJ IDEA

Ever since its release back in the distant universe of 2018, I was a big fan and paying user of Comma IDE. Produced by the lovely folks over at Edument, Comma was both a full-fledged, standalone IDE as well as a plugin for IntelliJ IDEA-based IDEs.

In reality the standalone version and the plugin shared the same code. The standalone Comma IDE represented a mostly rare third-party derivation of the IntelliJ IDEA Community open source platform, whereas the plugin used the more common approach of simply extending the IntelliJ IDEA applications provided by JetBrains directly.

Unfortunately as times moved on, it had reportedly become more and more difficult for Edument to align the costs of keeping up with the continuously evolving IntelliJ platform with the income generated by the Comma offerings. You can read more about that in the official announcement of Comma’s development coming to an end.

A second life for Comma

While it was obviously sad news to hear about the discontinuation of Comma as a commercial product, the community was blessed by a source release of the plugin code. In combination with the forked version of the IntelliJ Community source (intellij-ide-fork), it was once again technically possible for the community to re-create Comma in both IDE and plugin form.

I kept that very possibility in the back of my mind as the days, weeks, and months ticked over after the discontinuation announcement in February. I have learned to tolerate Java as a result of my $day-job, but I was also willing to wait and see if some other willing soul would appear and revive Comma (in the words of management’s clueless response to revelations of efforts that required overtime, “work smarter, not harder”).

By August, however, I had the tuits to take the efforts up myself and so dove into trying to re-create the standalone Comma IDE.

The pace of change is certainly a thing

I quickly collided into a hard truth about the era of IntelliJ Community that Comma IDE was built against. The comma-223.4884 branch of intellij-ide-fork is 79,982 commits behind the code in the intellij-comunity repository. Those ~80k commits represent only about 24 months of activity. That’s a lot of change!

One of the most crucial changes that was being undertaken over those months, and actually over several years preceding them, was the transition from a build system called JPS to the Gradle IntelliJ Plugin. From what I understand, JPS requires a local checkout fo intellij-community (or a custom fork such as we find in intellij-ide-fork) — something that the Gradle IntelliJ Plugin does not.

Unfortunately, there is no current way to use Gradle IntelliJ Plugin to build a standalone IDE. (The documentation implies that this is an eventual goal, but I’ve heard that this page has been in place for years without update).

I spent two weeks trying and failing to make any sensible progress with JPS and a checkout of intellij-ide-fork rebased onto the latest intellij-community before finally setting aside the dream of a standalone Comma (for now…).

Catching up

After switching over to the Gradle Intellij Plugin, progress went from non-existent to quite fast-paced. Switching from targeting standalone to developing a plugin was clearly the right choice for the moment.

There was another course correction which completely transformed the velocity of change…

IntelliJ provides an option in the Code menu: Convert Java File to Kotlin File. They should put up a warning that any current tolerance levels for writing Java code will almost certainly decrease precipitously. I know mine did! There’s just zero reason — in this or any universe — to start a new codebase in Java when you can write Kotlin and integrate with Java libraries seamlessly.

At some point I’m going to try the same trick with the Java implementation of NQP.

A somewhat complete list of changes

  • All references of Perl 6 replaced with Raku.
  • 49 Annotations were rewritten as Inspections, along with 34 associated Fixes.
    • Inspections can be selectively enabled/disabled from the IntelliJ settings.
  • New Raku project setup that uses the latest Kotlin UI DSL instead of deprecated internals.
  • Complete rewrite of META6.json handling based on Kotlin serialization.
  • Introduced a service that checks if the project contains the core Rakudo source.
    • If so, it prompts the user whether they want to disable a handful of inspections that can get thrown off by core source code (such as attributes introduced in the bootstrap but used in the class source).
  • Major cleanup and revision of services in general.
  • Complete rewrite of fetching the ecosystem.
  • Complete rewrite of package management.
  • Migration of storing the Raku SDK to today’s recommended approach.
  • Added NQP syntax highlighting.
  • Added ‘🦋’ menu for quickly setting the Raku SDK version and launching REPLs.

A few of the things yet to be addressed

  • Comma had quite a comprehensive test suite. Reanimate it!
  • Validate that all Cro-specific functionality is working as expected.
  • Look into some highlighting related parser issues.
  • Fix dependency management for names that use :auth, :ver, etc.
  • Add a UI for installing modules.
  • Migrate as many Java files to Kotlin as possible (some that need static can’t be reasonably converted yet)
  • Publish to the Jetbrains Marketplace.
  • Migrate the repo to the github.com/Raku community namespace.
  • Include some additional themes, if possible.

What’s in a name?

We’ve been calling the revitalized plugin comma-plugin 2.0. However, in consultation with Jonathan Worthington and with current users, the plan is to rename the plugin to simply Raku before publishing to the Jetbrains Marketplace.

This aligns with the naming scheme of most other programming language plugins for IntelliJ. It does have the disadvantage of being harder to reference in communication. Therefore I somewhat expect to keep using Comma or perhaps The Plugin to refer to the efforts in IRC and elsewhere.

Concluding thoughts

IntelliJ IDEA is a fast-moving target and coding a plugin for it has enlightened me to some of the dynamics that probably contributed to the cancellation of Comma as a commercial product.

That said, the IntelliJ IDEA Platform is quite a pleasure to code against. It is generally well-documented and has clearly benefited from two decades of evolution. That always carries some baggage but for the most part I felt like I was working with a really well-designed ecosystem of IDE subsystems.

Kotlin is an incredible leap forward for working with legacy Java code and the JVM. I was feeling like my Java code was hitting a decent stride in terms of concision and expressiveness.

After my fateful decision to use IntelliJ’s automatic Java-to-Kotlin converter, that feeling has significantly evaporated. Writing Java is back to feeling hopelessly verbose, even in its sharpest forms. (Forget about JDK < 17! Instead, write Kotlin and target those ancient versions instead.)

So, approach Kotlin with caution if you’re going to be forced to write Java at work. It’s still worth it, in my opinion. I’ll have to write some posts about what I like about it some other time.

Interested in giving the Raku plugin for IntelliJ a try? Please check out the source or download the latest release.

Day 19 – Wrapping Scripts

This is a cross post of https://dev.to/patrickbkr/better-wrapper-scripts-158j

When creating an application in Raku one will at one point typically hit the issue that the application can only be started by calling raku and passing a few arguments. The usual solution is to write a small wrapper shell script on POSIX and a bat script on Windows. But getting these wrapper scripts really right is not trivial. Typical pitfalls are:

  • The script depends on the current working directory
  • It requires Bash and thus can’t work in e.g. BusyBox
  • It fails to process args that contain special chars (e.g. < or > (or many others) combined with bat files is fun)
  • It insta returns to the prompt and then ghost writes to the terminal on Windows

A few years ago I started working on a tool to help generating robust wrappers for this use case. I’ve finally pushed it over the finish line.

I’ve named it the Executable Runner Generator. The name could have been chosen better, but we’ll have to live with it now. It’s written in Raku, but the generated wrappers are independent of the language. Currently it can generate wrappers that work on Windows x86_64 and all sorts of POSIX systems.

In general the task of the wrapper is to put together a command line (and runtime environment like CWD and env vars) and execute the program. How exactly that happens is configured via a set of options. The wrapper has the ability to:

  • Construct paths relative to the wrappers file system location
  • Change path separators on Windows
  • Change the current working directory
  • Specify the command line arguments to pass to the program
  • Forward the arguments passed to the wrapper
  • Add and remove environment variables

How?

Glad you asked! To install run

zef install Devel::ExecRunnerGenerator

Then create a file my-app-runner-config.json with these contents:

program => "<abs>bin/raku",
cwd => "<abs>.",
args => [
    "<abs>share/perl6/site/bin/my-app.raku",
    "<cmd-args>"
],
archs => [
    "posix",
    "windows-x86_64.exe",
],
out-path => ".",
:overwrite

Now run

exec-runner-generator --config=my-app-runner-config.json

Which should leave you with a posix and windows-x86_64.exe file. Congratulations!

For all the non-Raku folks out there, there even is a small webservice that exposes the functionality: https://boekernet.de/erg

Happy adventing everyone!

P.S.
On Windows the generator relies on a native executable written in C to do its magic. There is no exec syscall on Windows. The program works around this by staying alive, letting the child process inherit its file descriptors and once the child finishes, returns with its exit code.

I’m pretty sure this “exec emulation” isn’t perfect. But as I’m not a Windows low level expert I don’t even know what I’m missing. Signals? Security contexts? I don’t know.
So: If you are knowledgeable of the lower Windows APIs, I’d love to get feedback on the implementation or maybe even a patch. You can reach me on many channels.

Day 18 – Happy™ Xmas

Christmas was fast approaching and Santa was starting to worry about all the presents being wrapped in time. Ensuring that he could quickly find the Elfmail address for all of the team was important to whip them into shape.

Luckily, Rudolph [for it was he] had been learning raku and cro for some years now and he was ready to help. This year he had read about the wonderful HTMX framework and thought “If only I can combine HTMX and Cro and Raku in a cool way, I can make site build and maintenance a breeze.”

Now, since Rudolph was lazy, he didn’t really care about functional or object oriented coding style purity – he just wanted to get the job done fast and get back to his gin-sodden hay. And so he proceeded to dismay the other purile (should that be purist? -ed) elven coding communities such as Elflang and O-camel-ye-faithful and C++istmas by using the tool that worked best for the job.

Object Oriented

First, he knew that websites – especially those with HTMX – were written largely in HTML, so he wanted to write some HTML, but not too much.

To get some optional HTML, the Cro template <?.thead>...</?> tag; and some interpolated data, the <@results>...</@> and variable <.email> tags are great. Cro Template language is very nice for this – best tool for the job!

Here is a section of MyLib.rakumod

class ActiveTable is export {
    has THead() $.thead;

    method RENDER {
        q:to/END/
         <table class="striped">
             <?.thead>
                 <&THead(.thead)>
             </?>
             <tbody id="search-results">
             </tbody>
         </table>
     END
    }   
}

class Results is export {
    has @.results;

    method RENDER {
        q:to/END/
         <@results>
         <tr>
             <td><.firstName></td>
             <td><.lastName></td>
             <td><.email></td>
         </tr>
         </@>
     END
    }   
}

He wanted this HTML to be reusable, so he asked his friend to make a Cromponent prototype so he could make a library of reusable components – raku OO classes with attributes and (optionally) methods.

Functional

Then he thought – I am bored with HTML and OO. What other coding styles are there? Ah, yes I remember HTML::Functional – that looks like a cool new toy.

use Cromponent;
use Cromponent::MyLib;

my $cromponent = Cromponent.new;
my ($index, $topic);

{  #block to avoid namespace collision

    use HTML::Functional;

    $index =
        div [
            h3 [
                'Search Elves',
                span :class<htmx-indicator>, [ img :src</img/bars.svg>; '  Searching...' ]
            ];  

            input :class<form-control>, :type<search>, :name<search>,
                :placeholder<Begin typing to search elvesis>,
                :hx-post</happy_tm_xmas/search>,
                :hx-trigger<keyup changed delay:500ms, search>,
                :hx-target<#search-results>,
                :hx-indicator<.htmx-indicator>;

            activetable :thead<Given Elven Elfmail>, :$topic;
        ];      
}

Of course he had not forgotten the cool dynamic capabilities of HTMX – in this case the Active Search example. But he loved the way that he could compose HTML tags and Cromponents directly as raku source just like Elmlang – but on the server side!

Procedural

Rudolph was exhausted with all that heavy OO and Functional coding – so he just finished off with some plain old Procedural – phew!

use Cro::HTTP::Router;
use Cro::WebApp::Template;

sub happy_tm_xmas-routes() is export {

    route {

        $cromponent.add: Results, ActiveTable, THead, HCell, Row, Cell;

        get -> {
            template-with-components $cromponent, $index, $topic;
        }

        post -> 'search' {
            my $needle;

            request-body -> %fields {
                $needle = %fields<search>;
            }

            template-with-components $cromponent, results( results => search($needle), :$topic), $topic;
        }
    }
}

sub search($needle) {

    sub check($str) { $str.contains($needle, :i) };

    data.grep: (
        *.<firstName>.&check,
        *.<lastName>.&check,
        *.<email>.&check,
    ).any;
}

use JSON::Fast;

sub data() {

    from-json q:to/END/;
    [
        {"firstName": "Venus", "lastName": "Grimes", "email": "lectus.rutrum@Duisa.edu", "city": "Ankara"},
        {"firstName": "Fletcher", "lastName": "Owen", "email": "metus@Aenean.org", "city": "Niort"},
        {"firstName": "William", "lastName": "Hale", "email": "eu.dolor@risusodio.edu", "city": "Te Awamutu"},
        {"firstName": "TaShya", "lastName": "Cash", "email": "tincidunt.orci.quis@nuncnullavulputate.co.uk", "city": "Titagarh"},
       ...
    ]
    END
}

And, so that was it – a couple of Cro routes, serving the new template-with-components function to serve up the Cromponents. Some data and a search function to serve the results on the HTMX keyup trigger.

Happy™ Xmas

The proof of the pudding is in the viewing – so here is the finished result. If you would like to try this yourself, the code is available here: https://github.com/librasteve/raku-Cro-Website-Basic/tree/02-sharc1 … note that the branch is called 02-sharc1 for sass, htmx, raku, cro.

Credits

On a serious note, while it seems odd to apply three styles of coding in a few lines, a practical coder who wants to use the best tool for the job will appreciate that all these styles are there according to what works best.

Massive kudos to smokemachine for making an experiment with Cromponents – this post is just scraping the surface of what can be done. A better way would be to use Red for our data model and to have that inside the Cromponent – and to get the search results via a method on class ActiveTable {...}

These ideas will hopefully coalesce over on my Raku::Journey blog in 2025…

~librasteve (aka Rudolph)

Day 17 – Chebyshev Polynomials and Fitting Workflows

Introduction

This post demonstrates the use of Chebyshev polynomials in regression and curve fitting workflows. It highlights various Raku packages that facilitate these processes, providing insights into their features and applications.

TL;DR

  • Chebyshev polynomials can be computed exactly.
  • The “Math::Fitting” package yields functors.
  • Fitting utilizes a function basis.
  • Matrix formulas facilitate the computation of the fit (linear regression).
  • A real-life example is demonstrated using weather temperature data. For details, see the section before the last.

Setup

Here are the packages used in this post:

use Math::Polynomial::Chebyshev;
use Math::Fitting;
use Math::Matrix;

use Data::Generators;
use Data::Importers;
use Data::Reshapers;
use Data::Summarizers;
use Data::Translators;
use Data::TypeSystem;

use JavaScript::D3;
use JavaScript::Google::Charts;
use Text::Plot;

use Hash::Merge;

Why use Chebyshev polynomials in fitting?

Chebyshev polynomials provide a powerful and efficient basis for linear regression fitting, particularly when dealing with polynomial approximation and curve fitting. These polynomials, defined recursively, are a sequence of orthogonal polynomials that minimize the problem of Runge’s phenomenon, which is common with high-degree polynomial interpolation.

One of the key advantages of using Chebyshev polynomials in regression is their property of minimizing the maximum error between the fitted curve and the actual data points, known as the minimax property. Because of that property, more stable and accurate approximations are obtained, especially at the boundaries of the interval.

The orthogonality of Chebyshev polynomials with respect to the weight function w(x) = 1/sqrt(1-x^2) on the interval [-1, 1] ensures that the regression coefficients are uncorrelated, which simplifies the computation and enhances numerical stability. Furthermore, Chebyshev polynomials are excellent for approximating functions that are not well-behaved or have rapid oscillations, as they distribute approximation error more evenly across the interval.

Remark: This is one of the reasons Clenshaw-Curtis quadrature was one of the “main” quadrature rules I implemented in Mathematica’s NIntegrate.

Using Chebyshev polynomials into linear regression models allows for a flexible and robust function basis that can adapt to the complexity of the data while maintaining computational efficiency. This makes them particularly suitable for applications requiring high precision and stability, such as in signal processing, numerical analysis, and scientific computing.

Overall, the unique properties of Chebyshev polynomials make them a great regression tool, offering a blend of accuracy, stability, and efficiency.


Chebyshev polynomials computation

This section discusses the computation of Chebyshev polynomials using different methods and their implications on precision.

Computation Granularity

The computation over Chebyshev polynomials is supported on the interval . The recursive and trigonometric methods are compared to understand their impact on the precision of results.

<recursive trigonometric>
==> { .map({ $_ => chebyshev-t(3, 0.3, method => $_) }) }()
# (recursive => -0.792 trigonometric => -0.7920000000000003)

Here polynomial values are computed over a “dense enough” grid:

my $k = 12;
my $method = 'trig'; # 'trig'
my @x = (-1.0, -0.99 ... 1.0);
say '@x.elems : ', @x.elems;

my @data  = @x.map({ [$_, chebyshev-t($k, $_, :$method)]});
my @data1 = chebyshev-t($k, @x);

say deduce-type(@data);
say deduce-type(@data1);
# @x.elems : 201
# Vector(Tuple([Atom((Rat)), Atom((Numeric))]), 201)
# Vector((Any), 201)

Residuals with trigonometric and recursive methods are utilized to assess precision:

sink records-summary(@data.map(*.tail) <<->> @data1)
# +----------------------------------+
# | numerical                        |
# +----------------------------------+
# | 3rd-Qu => 3.3306690738754696e-16 |
# | Max    => 3.4416913763379853e-15 |
# | Median => -3.469446951953614e-18 |
# | 1st-Qu => -6.661338147750939e-16 |
# | Min    => -3.774758283725532e-15 |
# | Mean   => -8.803937402208662e-17 |
# +----------------------------------+

Precision

The exact Chebyshev polynomial values can be computed using FatRat numbers, ensuring high precision in numerical computations.

my $v = chebyshev-t(100, <1/4>.FatRat, method => 'recursive')
# 0.9908630290911637341902191815340830456

The numerator and denominator of the computed result are:

say $v.numerator;
say $v.denominator;
# 2512136227142750476878317151377
# 2535301200456458802993406410752

Plots

This section demonstrates plotting Chebyshev polynomials using Google Charts via the “JavaScript::Google::Charts” package.

Single Polynomial

A single polynomial can be plotted using a Line chart:

my $n = 6;
my @data = chebyshev-t(6, (-1, -0.98 ... 1).List);
# [1 0.3604845076 -0.1315856097 -0.4953170862 -0.748302037 -0.906688 -0.9852465029 -0.9974401556 -0.9554882683 -0.8704309944 -0.752192 -0.6096396575 -0.4506467656 ...]
#%html
js-google-charts('LineChart', @data, 
    title => "Chebyshev-T($n) polynomial", 
    :$titleTextStyle, :$backgroundColor, :$chartArea, :$hAxis, :$vAxis,
    width => 800, 
    div-id => 'poly1', :$format,
    :png-button)

Basis

In fitting, bases of functions are crucial. The first eight Chebyshev-T polynomials are plotted to illustrate this.

my $n = 8;
my @data = (-1, -0.98 ... 1).map: -> $x {
    [x => $x, |(0..$n).map({
         .Str => chebyshev-t($_, $x, :$method)
     })].Hash
}

deduce-type(@data):tally;
# Tuple([Struct([0, 1, 2, 3, 4, 5, 6, 7, 8, x], [Num, Num, Num, Num, Num, Num, Num, Num, Num, Int]) => 1, Struct([0, 1, 2, 3, 4, 5, 6, 7, 8, x], [Num, Num, Num, Num, Num, Num, Num, Num, Num, Rat]) => 100], 101)

The plot with all eight functions is shown below:

#%html
js-google-charts('LineChart', @data,
    column-names => ['x', |(0..$n)».Str],
    title => "Chebyshev T polynomials, 0 .. $n",
    :$titleTextStyle,
    width => 800, 
    height => 400,
    :$backgroundColor, :$hAxis, :$vAxis,
    legend => merge-hash($legend, %(position => 'right')),
    chartArea => merge-hash($chartArea, %(right => 100)),
    format => 'html', 
    div-id => "cheb$n",
    :$format,
    :png-button)

Text Plot

Text plots provide a reliable method for visualizing data anywhere! The data is converted into a long form to facilitate plotting using “Text::Plot”.

my @dataLong = to-long-format(@data, <x>).sort(*<Variable x>);
deduce-type(@dataLong):tally
# Tuple([Struct([Value, Variable, x], [Num, Str, Int]) => 9, Struct([Value, Variable, x], [Num, Str, Rat]) => 900], 909)

A sample of the data is provided:

@dataLong.pick(8)
  ==> {.sort(*<Variable x>)}()
  ==> to-html(field-names => <Variable x Value>)
VariablexValue
1-0.18-0.18
3-0.440.979264
30.66-0.830016
6-0.92-0.7483020369919988
60.560.9111532625919998
8-0.60.42197247999999865
80.080.8016867058843643
80.660.8694861561561088

The text plot is presented here:

my @chebInds = 1, 2, 3, 4;
my @dataLong3 = @dataLong.grep({
    $_<Variable>.Int ∈ @chebInds
}).classify(*<Variable>).map({
    .key => .value.map(*<x Value>).Array
}).sort(*.key)».value;

text-list-plot(@dataLong3,
  width => 100,
  height => 25,
  title => "Chebyshev T polynomials, 0 .. $n",
  x-label => (@chebInds >>~>> ' : ' Z~ <* □ ▽ ❍>).join(', ')
);
# Chebyshev T polynomials, 0 .. 8                                   
# +----+---------------------+---------------------+---------------------+---------------------+-----+      
# |                                                                                                  |      
# +    ❍                  ▽▽▽▽▽▽▽▽               ❍❍❍❍❍❍                                       *❍     +  1.00
# |     □              ▽▽▽        ▽▽▽         ❍❍❍      ❍❍❍                               ***** □     |      
# |      □□          ▽▽              ▽▽     ❍❍            ❍                          ****    □□▽     |      
# |     ❍  □        ▽▽                 ▽▽▽ ❍               ❍❍                   *****       □ ▽❍     |      
# |         □      ▽                     ❍❍▽                 ❍❍             *****          □         |      
# +          □□   ▽                     ❍  ▽▽                  ❍        ****             □□  ▽       +  0.50
# |      ❍    □  ▽                     ❍     ▽                  ❍  *****                □   ▽ ❍      |      
# |            ▽▽                    ❍❍       ▽▽               **❍*                    □             |      
# |       ❍      □□                 ❍           ▽▽        *****   ❍                  □□    ▽ ❍       |      
# |           ▽    □                ❍             ▽▽  *****        ❍                □     ▽          |      
# +        ❍  ▽     □□             ❍              *▽**              ❍             □□     ▽  ❍        +  0.00
# |          ▽       □□           ❍          *****  ▽▽               ❍          □□                   |      
# |         ❍          □□        ❍       ****         ▽▽              ❍        □□       ▽  ❍         |      
# |                      □□    ❍❍   *****               ▽              ❍❍    □□        ▽             |      
# |        ▽ ❍             □□ ❍ *****                    ▽▽              ❍ □□         ▽▽  ❍          |      
# +       ▽   ❍             *❍□*                          ▽▽             ❍□          ▽   ❍           + -0.50
# |                    ***** ❍ □□□                          ▽▽        □□□ ❍         ▽                |      
# |      ▽    ❍    ****    ❍❍     □□□                         ▽▽   □□□     ❍❍     ▽▽    ❍            |      
# |     ▽     *❍❍**      ❍❍         □□□□                        ▽▽□          ❍❍ ▽▽     ❍             |      
# |       *****  ❍     ❍❍               □□□□□             □□□□□□  ▽▽▽▽        ▽❍❍     ❍              |      
# +    ▽**        ❍❍❍❍❍                      □□□□□□□□□□□□□□           ▽▽▽▽▽▽▽▽  ❍❍❍❍❍❍               + -1.00
# |                                                                                                  |      
# +----+---------------------+---------------------+---------------------+---------------------+-----+      
#      -1.00                 -0.50                 0.00                  0.50                  1.00         
#                                      1 : *, 2 : □, 3 : ▽, 4 : ❍

Fitting

This section presents the generation of “measurements data” with noise and the fitting process using a function basis.

my @temptimelist = 0.1, 0.2 ... 20;
my @tempvaluelist = @temptimelist.map({
    sin($_) / $_
}) Z+ (1..200).map({
    (3.rand - 1.5) * 0.02
});
my @data1 = @temptimelist Z @tempvaluelist;
@data1 = @data1.deepmap({ .Num });

deduce-type(@data1)
# Vector(Vector(Atom((Numeric)), 2), 200)

Rescaling of x-coordinates is performed as follows:

my @data2 = @data1.map({ my @a = $_.clone; @a[0] = @a[0] / max(@temptimelist); @a });

deduce-type(@data2)
# Vector(Vector(Atom((Numeric)), 2), 200)

A summary of the data is provided:

sink records-summary(@data2)
# +------------------+----------------------------------+
# | 0                | 1                                |
# +------------------+----------------------------------+
# | Min    => 0.005  | Min    => -0.23878758770507946   |
# | 1st-Qu => 0.2525 | 1st-Qu => -0.053476022454404415  |
# | Mean   => 0.5025 | Mean   => 0.07323149609113122    |
# | Median => 0.5025 | Median => -0.0025316517415275193 |
# | 3rd-Qu => 0.7525 | 3rd-Qu => 0.07666085422352723    |
# | Max    => 1      | Max    => 1.0071290191857256     |
# +------------------+----------------------------------+

The data is plotted below:

#% html
js-google-charts("Scatter", @data2, 
    title => 'Measurements data with noise',
    :$backgroundColor, :$hAxis, :$vAxis,
    :$titleTextStyle, :$chartArea,
    width => 800, 
    div-id => 'data', :$format,
    :png-button)

A function to rescale from [0,1] to [-1, 1] is defined:

my &rescale = { ($_ - 0.5) * 2 };

The basis functions are listed:

my @basis = (^16).map: { chebyshev-t($_) o &rescale }
@basis.elems
# 16

Remark: The function composition operator o is utilized above. The argument is rescaled before computing the Chebyshev polynomial value.

A linear model fit is computed using these functions:

my &lm = linear-model-fit(@data2, :@basis)
# Math::Fitting::FittedModel(type => linear, data => (200, 2), response-index => 1, basis => 16)

The best fit parameters are:

&lm('BestFitParameters')
# [0.18012514065989924 -0.3439467053791086 0.29469719162086117 -0.20515007850826206 0.12074121627488964 -0.003435776130307378 -0.047297896072549465 0.08663571434303828 -0.058165484141402886 -0.03933300920226471 0.03907623399167609 0.0015109810557268964 -0.011525135506292928 -0.0045136819929066 0.0021477767328720826 -0.004624145810439574]

The plot of these parameters is shown:

#% html
js-google-charts("Bar", &lm('BestFitParameters'), 
    :!horizontal,
    title => 'Best fit parameters',
    :$backgroundColor, 
    hAxis => merge-hash($hAxis, {title => 'Basis function index'}), 
    vAxis => merge-hash($hAxis, {title => 'Coefficient'}), 
    :``titleTextStyle, :``chartArea,
    width => 800, 
    div-id => 'bestFitParams', :$format,
    :png-button)

It is observed from the plot that using more than 12 basis functions does not improve the fit, as coefficients beyond the 12th index are very small.

The data and the fit are plotted after preparing the plot data:

my @fit = @data2.map(*.head)».&lm;
my @plotData = transpose([@data2.map(*.head).Array, @data2.map(*.tail).Array, @fit]);
@plotData = @plotData.map({ <x data fit>.Array Z=> $_.Array })».Hash;

deduce-type(@plotData)
# Vector(Assoc(Atom((Str)), Atom((Numeric)), 3), 200)

The plot is presented here:

#% html
js-google-charts('ComboChart', 
    @plotData, 
    title => 'Data and fit',
    column-names => <x data fit>,
    :``backgroundColor, :``titleTextStyle :``hAxis, :``vAxis,
    seriesType => 'scatter',
    series => {
        0 => {type => 'scatter', pointSize => 2, opacity => 0.1, color => 'Gray'},
        1 => {type => 'line'}
    },
    legend => merge-hash($legend, %(position => 'bottom')),
    :$chartArea,
    width => 800, 
    div-id => 'fit1', :$format,
    :png-button)

The residuals of the last fit are computed:

sink records-summary( (@fit <<->> @data2.map(*.tail))».abs )
# +----------------------------------+
# | numerical                        |
# +----------------------------------+
# | Max    => 0.03470224056776856    |
# | Median => 0.0136727625440904     |
# | Min    => 0.00011187750898611348 |
# | 1st-Qu => 0.006365201141942046   |
# | Mean   => 0.01363628423382272    |
# | 3rd-Qu => 0.019937969354319008   |
# +----------------------------------+

Condition Number

The Ordinary Least Squares (OLS) fit is computed using the formula:

β=(t(X).X)^(-1).t(X).y, where t(X) is the transpose of X.

The condition number of the “normal matrix” (or “Gram matrix”) t(X).X is examined. The design matrix is obtained first:

my @a = &lm.design-matrix();
my $X = Math::Matrix.new(@a);
$X.size
# (200 16)

The Gram matrix is:

my $g = $X.transposed.dot-product($X);
$g.size
# (16 16)

The condition number of this matrix is:

$g.condition
# 88.55110861577737

It is concluded that the design matrix is suitable for use.

Remark: For a system of linear equations in matrix form Ax=b, the condition number of A, k(A), is defined as the maximum ratio of the relative error in x to the relative error in b.

Remark: Typically, if the condition number is k(A)=10^d, up to d digits of accuracy may be lost in addition to any loss caused by the numerical method (due to precision issues in arithmetic calculations).

Remark: A very “Raku-way” to define an ill-conditioned matrix is as “almost not of full rank” or “almost as if its inverse does not exist.”


Temperature Data

The entire workflow is repeated with real-life data, specifically weather temperature data for four consecutive years in Greenville, South Carolina, USA. This location is where the Perl and Raku Conference 2025 will be held.

The time series data is ingested:

my $url = 'https://raw.githubusercontent.com/antononcube/RakuForPrediction-blog/refs/heads/main/Data/dsTemperature-Greenville-SC-USA.csv';

my @dsTemperature = data-import($url, headers => 'auto');
@dsTemperature = @dsTemperature.deepmap({ ``_ ~~ / ^ \d+ '-' / ?? DateTime.new(``_) !! $_.Num });
deduce-type(@dsTemperature)
# Vector(Struct([AbsoluteTime, Date, Temperature], [Num, DateTime, Num]), 1462)

A summary of the data is shown:

sink records-summary(
  @dsTemperature, field-names => <Date AbsoluteTime Temperature>
);
# +--------------------------------+----------------------+------------------------------+
# | Date                           | AbsoluteTime         | Temperature                  |
# +--------------------------------+----------------------+------------------------------+
# | Min    => 2018-01-01T00:00:37Z | Min    => 3723753600 | Min    => -5.72              |
# | 1st-Qu => 2019-01-01T00:00:37Z | 1st-Qu => 3755289600 | 1st-Qu => 10.5               |
# | Mean   => 2020-01-01T12:00:37Z | Mean   => 3786868800 | Mean   => 17.053549931600518 |
# | Median => 2020-01-01T12:00:37Z | Median => 3786868800 | Median => 17.94              |
# | 3rd-Qu => 2021-01-01T00:00:37Z | 3rd-Qu => 3818448000 | 3rd-Qu => 24.11              |
# | Max    => 2022-01-01T00:00:37Z | Max    => 3849984000 | Max    => 29.89              |
# +--------------------------------+----------------------+------------------------------+

The plot of the data is provided:

#% html
js-google-charts("Scatter", @dsTemperature.map(*<Date Temperature>), 
    title => 'Temperature of Greenville, SC, USA',
    :$backgroundColor,
    hAxis => merge-hash($hAxis, {title => 'Time', format => 'M/yy'}), 
    vAxis => merge-hash($hAxis, {title => 'Temperature, ℃'}), 
    :``titleTextStyle, :``chartArea,
    width => 1200, 
    height => 400, 
    div-id => 'tempData', :$format,
    :png-button)

The fit is performed with rescaling:

my (``min, ``max) = @dsTemperature.map(*<AbsoluteTime>).Array.&{
  (.min, .max)
}();
# (3723753600 3849984000)
my &rescale-time = {
    -(``max + ``min) / (``max - ``min) + (2 * ``_) / (``max - $min)
}
my @basis = (^16).map({ chebyshev-t($_) o &rescale-time });
@basis.elems
# 16
my &lm-temp = linear-model-fit(
  @dsTemperature.map(*<AbsoluteTime Temperature>), :@basis
);
# Math::Fitting::FittedModel(type => linear, data => (1462, 2), response-index => 1, basis => 16)

The plot of the time series and the fit is presented:

my @fit = @dsTemperature.map(*<AbsoluteTime>)».&lm-temp;
my @plotData = transpose([
  @dsTemperature.map(*<AbsoluteTime>).Array,
  @dsTemperature.map(*<Temperature>).Array,
  @fit
]);
@plotData = @plotData.map({ <x data fit>.Array Z=> $_.Array })».Hash;

deduce-type(@plotData)
# Vector(Assoc(Atom((Str)), Atom((Numeric)), 3), 1462)
#% html

my @ticks = @dsTemperature.map({
    %( v => ``_<AbsoluteTime>, f => ``_<Date>.Str.substr(^7))
})».Hash[0, 120 ... *];

js-google-charts('ComboChart', 
    @plotData,
    title => 'Temperature data and Least Squares fit',
    column-names => <x data fit>,
    :``backgroundColor, :``titleTextStyle,
    hAxis => merge-hash($hAxis, {title => 'Time', :@ticks, textPosition => 'in'}), 
    vAxis => merge-hash($hAxis, {title => 'Temperature, ℃'}), 
    seriesType => 'scatter',
    series => {
        0 => {type => 'scatter', pointSize => 3, opacity => 0.1, color => 'Gray'},
        1 => {type => 'line', lineWidth => 4}
    },
    legend => merge-hash($legend, %(position => 'bottom')),
    :$chartArea,
    width => 1200, 
    height => 400, 
    div-id => 'tempDataFit', :$format,
    :png-button)

Future Plans

The current capabilities of Raku in performing regression analysis for both educational and practical purposes have been demonstrated.

Future plans include implementing computational frameworks for Quantile Regression in Raku. Additionally, the workflow code in this post can be generated using Large Language Models (LLMs), which will be explored soon.