--- title: "Perl 6 - Introduction to application programming" date: 2018-03-20 11:08:00 tags: Tutorial Perl6 Assixt GTK Programming Raku layout: post authors: - ["Patrick Spek", "https://tyil.nl"] --- = Perl 6 - Introduction to application programming :toc: preamble In this tutorial, I'll be guiding you through creating a simple application in Perl 6. If you don't have Perl 6 installed yet, get the http://rakudo.org/how-to-get-rakudo/[Rakudo Star] distribution for your OS. Alternatively, you can use the https://hub.docker.com/_/rakudo-star/[Docker image]. The application itself will be a simple dice-roller. You give it a number of dice to roll, and the number of sides the die has. We'll start off by creating it as a console application, then work to make it a GUI as well with the `GTK::Simple` module. == Preparation First, you'll want to install the libgtk headers. How to get these depends on your distro of choice. For Debian-based systems, which includes Ubuntu and derivatives, this command would be the following `apt` invocation: [source] ---- $ apt install libgtk-3-dev ---- For other distros, please consult your documentation. To ease up module/application building, I'll use `App::Assixt`. This module eases up on common tasks required for building other modules or applications. So we'll start by installing this module through `zef`. [source] ---- $ zef install App::Assixt ---- [NOTE] ==== You may need to rehash your `$PATH` as well, which can be done using `hash -r` on `bash`, or `rehash` for `zsh`. For other shells, consult your manual. ==== Next up, we can use `assixt` to create the new skeleton of our application, with the `new` subcommand. This will ask for some user input, which will be recorded in the `META6.json`, a json-formatted file to keep track of meta information about the module. `assixt` should take care of this file for you, so you never need to actually deal with it. [source] ---- $ assixt new ---- === assixt input Since the `assixt new` command requires some input, I'll walk through these options and explain how these would affect your eventual application. ==== Name of the module This is the name given to the module. This will be used for the directory name, which by default in `assixt` will be `perl6-` prepended to a lower-case version of the module name. If you ever wish to make a module that is to be shared in the Perl 6 ecosystem, this should be unique across the entire ecosystem. If you're interested in some guidelines, the https://pause.perl.org/pause/query?ACTION=pause_namingmodules[PAUSE guidelines] seem to apply pretty well to Perl 6 as well. For this application, we'll use `Local::App::Dicer`, but you can use whatever name you'd prefer here. ==== Your name Your name. This will be used as the author's name in the `META6.json`. It is used to find out who made it, in order to report issues (or words of praise, of course). ==== Your email address Your email address. Like your name, it will be used in case someone has to contact you in regards off the module. ==== Perl 6 version This defaults to `c` right now, and you can just hit enter to accept it. In the future, there will be a Perl 6.d available as well, in which case you can use this to indicate you want to use the newer features introduced in 6.d. This is not the case yet, so you just want to go with the default `c` value here. ==== Module description A short description of your module, preferably a single sentence. This is useful to people wondering what the module is for, and module managers can show to the user. ==== License key This indicates the license under which your module is distributed. This defaults to `GPL-3.0`, which I strongly recommend to use. The de-facto default seems to be `Artistic-2.0`, which is also used for Perl 6 itself. This identifier is based on the https://spdx.org/licenses/[SPDX license list]. Anything not mentioned in this list is not acceptable. #TODO Clarify why == Writing your first test With the creation of the directory structure and metadata being taken care of by `assixt`, we can now start on writing things. Tests are not mandatory, but are a great tool for quickly checking if everything works. If you make larger applications, it really helps not having to manually test anything. Another benefit is that you can quickly see if your changes, or those of someone else, break anything. Creating the base template for tests, `assixt` can help you out again: `assixt touch` can create templates in the right location, so you don't have to deal with it. In this case we want to create a test, which we'll call "basic". [source] ---- $ assixt touch test basic ---- This will create the file `t/basic.t` in your module directory. Its contents will look as follows: [source,perl6] ---- #! /usr/bin/env perl6 use v6.c; use Test; ok True; done-testing; # vim: ft=perl6 ---- The only test it has right now is `ok True`, which will always pass testing. We will change that line into something more usable for this application: [source,perl6] ---- use Local::App::Dicer; plan 2; subtest "Legal rolls", { plan 50; for 1..50 { ok 1 ≤ roll($_) ≤ $_, "Rolls between 1 and $_"; } } subtest "Illegal rolls", { plan 3; throws-like { roll(0) }, X::TypeCheck::Binding::Parameter, "Zero is not accepted"; throws-like { roll(-1) }, X::TypeCheck::Binding::Parameter, "Negative rolls are not accepted"; throws-like { roll(1.5) }, X::TypeCheck::Binding::Parameter, "Can't roll half sides"; } ---- [NOTE] ==== Perl 6 allows mathematical characters to make your code more concise, as with the ≤ in the above block. If you use http://www.vim.org/[vim], you can make use of the https://github.com/vim-perl/vim-perl6[vim-perl6] plugin, which has an option to change the longer, ascii-based ops (in this case `\<=`) into the shorter unicode based ops (in this case `≤`). This specific feature requires `let g:perl6_unicode_abbrevs = 1` in your `vimrc` to be enabled with `vim-perl6`. If that's not an option, you can use a https://en.wikipedia.org/wiki/Compose_key[compose key]. If that is not viable either, you can also stick to using the ascii-based ops. Perl 6 supports both of them. ==== This will run 53 tests, split up in two https://docs.perl6.org/language/testing#Grouping_tests[subtests]. Subtests are used to logically group your tests. In this case, the calls that are correct are in one subtest, the calls that should be rejected are in another. The `plan` keywords indicate how many tests should be run. This will help spot errors in case your expectations were not matched. For more information on testing, check out https://docs.perl6.org/language/testing[the Perl 6 docs on testing]. We're making use of two test routines, `ok` and `throws-like`. `ok` is a simple test: if the given statement is truthy, the test succeeds. The other one, `throws-like`, might require some more explanation. The first argument it expects is a code block, hence the `{ }`. Inside this block, you can run any code you want. In this case, we run code that we know shouldn't work. The second argument is the exception it should throw. The test succeeds if the right exception is thrown. Both `ok` and `throws-like` accept a descriptive string as optional last argument. === Running the tests A test is useless if you can't easily run it. For this, the `prove` utility exists. You can use `assixt test` to run these tests properly as well, saving you from having to manually type out the full `prove` command with options. [source] ---- $ assixt test ---- You might notice the tests are currently failing, which is correct. The `Local::App::Dicer` module doesn't exist yet to test against. We'll be working on that next. [NOTE] ==== For those interested, the command run by `assixt test` is `prove -e "perl6 -Ilib" t`. This will include the `lib` directory into the `PERL6PATH` to be able to access the libraries we'll be making. The `t` argument specifies the directory containing the tests. ==== == Creating the library Again, let's start with a `assixt` command to create the base template. This time, instead of `touch test`, we'll use `touch lib`. [source] ---- $ assixt touch unit Local::App::Dicer ---- This will generate a template file at `lib/Local/App/Dicer.pm6` which some defaults set. The file will look like this. [source,perl6] ---- #! /usr/bin/env false use v6.c; unit module Local::App::Dicer; ---- The first line is a https://en.wikipedia.org/wiki/Shebang_(Unix)[shebang]. It informs the shell what to do when you try to run the file as an executable program. In this case, it will run `false`, which immediately exits with a non-success code. This file needs to be run as a Perl 6 module file, and running it as a standalone file is an error. The `use v6.c` line indicates what version of Perl 6 should be used, and is taken from the `META6.json`, which was generated with `assixt new`. The last line informs the name of this module, which is `Local::App::Dicer`. Beneath this, we can add subroutines, which can be exported. These can then be accessed from other Perl 6 files that `use` this module. === Creating the `roll` subroutine Since we want to be able to `roll` a die, we'll create a subroutine to do exactly that. Let's start with the signature, which tells the compiler the name of the subroutine, which arguments it accepts, their types and what type the subroutine will return. [TIP] ==== Perl 6 is gradually typed, so all type information is optional. The subroutine arguments are optional too, but you will rarely want a subroutine that doesn't have an argument list. ==== [source,perl6] ---- sub roll($sides) is export { $sides } ---- Let's break this down. - `sub` informs the compiler we're going to create a subroutine. - `roll` is the name of the subroutine we're going to create. - `$sides` defines an argument used by the subroutine. - `is export` tells the compiler that this subroutine is to be exported. This allows access to the subroutine to another program that imports this module through a `use`. - `{ $sides }` is the subroutine body. In Perl 6, the last statement is also the return value in a code block, thus this returns the value of $sides. A closing `;` is also not required for the last statement in a block. If you run `assixt test` now, you can see it only fails 1/2 subtests: [source] ---- # TODO: Add output of failing tests ---- Something is going right, but not all of it yet. The 3 tests to check for illegal rolls are still failing, because there's no constraints on the input of the subroutine. === Adding constraints The first constraint we'll add is to limit the value of `$sides` to an `Int:D`. The first part of this constraint is common in many languages, the `Int` part. The `:D` requires the argument to be **defined**. This forces an actual existing instance of `Int`, not a `Nil` or undefined value. [source,perl6] ---- sub roll(Int:D $sides) is export ---- Fractional input is no longer allowed, since an `Int` is always a round number. But an `Int` is still allowed to be 0 or negative, which isn't possible in a dice roll. Nearly every language will make you solve these two cases in the subroutine body. But in Perl 6, you can add another constraint in the signature that checks for exactly that: [source,perl6] ---- sub roll(Int:D $sides where $sides > 0) is export ---- The `where` part specifies additional constraints, in this case `$sides > 0`. So now, only round numbers larger than 0 are allowed. If you run `assixt test` again, you should see all tests passing, indicating that all illegal rolls are now correctly disallowed. === Returning a random number So now that we can be sure that the input is always correct, we can start on making the output more random. In Perl 6, you can take a number and call `.rand` on it, to get a random number between 0 and the value of the number you called it on. This in turn can be rounded up to get a number ranging from 1 to the value of the number you called `.rand` on. These two method calls can also be changed to yield concise code: [source,perl6] ---- sub roll(Int:D $sides where $sides > 0) is export { $sides.rand.ceiling } ---- That's all we need from the library itself. Now we can start on making a usable program out of it. == Adding a console interface First off, a console interface. `assixt` can `touch` a starting point for an executable script as well, using `assixt touch bin`: [source] ---- $ assixt touch bin dicer ---- This will create the file `bin/dicer` in your repository, with the following template: [source,perl6] ---- #! /usr/bin/env perl6 use v6.c; sub MAIN { … } ---- The program will run the `MAIN` sub by default. We want to slightly change this `MAIN` signature though, since we want to accept user input. And it just so happens that you can specify the command line parameters in the `MAIN` signature in Perl 6. This lets us add constraints to the parameters and give them better names with next to no effort. We want to accept two numbers, one for the number of dice, and one for the number of sides per die: [source,perl6] ---- sub MAIN(Int:D $dice, Int:D $sides where { $dice > 0 && $sides > 0 }) ---- Here we see the `where` applying constraints again. If you try running this program in its current state, you'll have to run the following: [source] ---- $ perl6 -Ilib bin/dicer Usage: bin/dicer ---- This will return a list of all possible ways to invoke the program. There's one slight problem right now. The usage description does not inform the user that both arguments need to be larger than 0. We'll take care of that in a moment. First we'll make this part work the way we want. To do that, let's add a `use` statement to our `lib` directory, and call the `roll` function we created earlier. The `bin/dicer` file will come to look as follows: [source,perl6] ---- #! /usr/bin/env perl6 use v6.c; use Local::App::Dicer; sub MAIN(Int:D $dice, Int:D $sides where { $dice > 0 && $sides > 0 }) { say $dice × roll($sides) } ---- [NOTE] ==== Just like the `≤` character, Perl 6 allows to use the proper multiplication character `×` (this is not the letter `x`!). You can use the more widely known `*` for multiplication as well. ==== If you run the program with the arguments `2` and `20` now, you'll get a random number between 2 and 40, just like we expect: [source] ---- $ perl6 -Ilib bin/dicer 2 20 18 ---- === The usage output Now, we still have the trouble of illegal number input not clearly telling what's wrong. We can do a neat trick with https://docs.perl6.org/language/functions#index-entry-USAGE[the USAGE sub] to achieve this. Perl 6 allows a subroutine with the name `USAGE` to be defined, overriding the default behaviour. Using this, we can generate a friendlier message informing the user what they need to supply more clearly. The `USAGE` sub would look like this: [source,perl6] ---- sub USAGE { say "Dicer requires two positive, round numbers as arguments." } ---- If you run the program with incorrect parameters now, it will show the text from the `USAGE` subroutine. If the parameters are correct, it will run the `MAIN` subroutine. You now have a working console application in Perl 6! == Making a simple GUI But that's not all. Perl 6 has a module to create GUIs with the https://www.gtk.org/[GTK library] as well. For this, we'll use the https://github.com/perl6/gtk-simple[`GTK::Simple`] module. You can add this module as a dependency to the `Local::App::Dicer` repository with `assixt` as well, using the `depend` command. By default, this will also install the dependency locally so you can use it immediately. [source] ---- $ assixt depend GTK::Simple ---- === Multi subs Next, we could create another executable file and call it `dicer-gtk`. However, I can also use this moment to introduce https://docs.perl6.org/language/glossary#index-entry-multi-method[multi methods]. These are subs with the same name, but differing signatures. If a call to such a sub could potentially match multiple signatures, the most specific one will be used. We will add another `MAIN` sub, which will be called when `bin/dicer` is called with the `--gtk` parameter. We should also update the `USAGE` sub accordingly, of course. And while we're at it, let's also include the `GTK::Simple` and `GTK::Simple::App` modules. The first pulls in all the different GTK elements we will use later on, while the latter pulls in the class for the base GTK application window. The updated `MAIN`, `USAGE` and `use` parts will now look like this: [source,perl6] ---- use Local::App::Dicer; use GTK::Simple; use GTK::Simple::App; multi sub MAIN(Int:D $dice, Int:D $sides where { $dice > 0 && $sides > 0 }) { say $dice × roll($sides) } multi sub MAIN(Bool:D :$gtk where $gtk == True) { # TODO: Create the GTK version } sub USAGE { say "Launch Dicer as a GUI with --gtk, or supply two positive, round numbers as arguments."; } ---- There's a new thing in a signature header here as well, `:$gtk`. The `:` in front of it makes it a named argument, instead of a positional one. When used in a `MAIN`, this will allow it to be used like a long-opt, thus as `--gtk`. Its use in general subroutine signatures is explained in the next chapter. Running the application with `--gtk` gives no output now, because the body only contains a comment. Let's fix that. === Creating the window First off, we require a `GTK::Simple::App` instance. This is the main window, in which we'll be able to put elements such as buttons, labels, and input fields. We can create the `GTK::Simple::App` as follows: [source,perl6] ---- my GTK::Simple::App $app .= new(title => "Dicer"); ---- This one line brings in some new Perl 6 syntax, namely the `.=` operator. There's also the use of a named argument in a regular subroutine. The `.=` operator performs a method on the variable on the left. In our case, it will call the `new` subroutine, which creates a new instance of the `GTK::Simple::App` class. This is commonly referred to as the **constructor**. The named argument list (`title \=> "Dicer"`) is another commonly used feature in Perl 6. Any method can be given a non-positional, named parameter. This is done by appending a `:` in front of the variable name in the sub signature. This has already been used in our code, in `multi sub MAIN(Bool :$gtk where $gtk == True)`. This has a couple of benefits, which are explained in the https://docs.perl6.org/type/Signature#index-entry-positional_argument_%28Signature%29_named_argument_%28Signature%29[Perl 6 docs on signatures]. === Creating the elements Next up, we can create the elements we'd like to have visible in our application window. We needed two inputs for the console version, so we'll probably need two for the GUI version as well. Since we have two inputs, we want labels for them. The roll itself will be performed on a button press. Lastly, we will want another label to display the outcome. This brings us to 6 elements in total: - 3 labels - 2 entries - 1 button [source,perl6] ---- my GTK::Simple::Label $label-dice .= new(text => "Amount of dice"); my GTK::Simple::Label $label-sides .= new(text => "Dice value"); my GTK::Simple::Label $label-result .= new(text => ""); my GTK::Simple::Entry $entry-dice .= new(text => 0); my GTK::Simple::Entry $entry-sides .= new(text => 0); my GTK::Simple::Button $button-roll .= new(label => "Roll!"); ---- This creates all elements we want to show to the user. === Show the elements in the application window Now that we have our elements, let's put them into the application window. We'll need to put them into a layout as well. For this, we'll use a grid. The `GTK::Simple::Grid` constructor takes pairs, with the key being a tuple containing 4 elements, and the value containing the element you want to show. The tuple's elements are the `x`, `y`, `w` and `h`, which are the x coordinates, y coordinates, width and height respectively. This in turn takes us to the following statement: [source,perl6] ---- $app.set-content( GTK::Simple::Grid.new( [0, 0, 1, 1] => $label-dice, [1, 0, 1, 1] => $entry-dice, [0, 1, 1, 1] => $label-sides, [1, 1, 1, 1] => $entry-sides, [0, 2, 2, 1] => $button-roll, [0, 3, 2, 1] => $label-result, ) ); ---- Put a `$app.run` beneath that, and try running `perl6 -Ilib bin/dicer --gtk`. That should provide you with a GTK window with all the elements visible in the position we want. To make it a little more appealing, we can add a `border-width` to the `$app`, which adds a margin between the border of the application window, and the grid inside the window. [source,perl6] ---- $app.border-width = 20; $app.run; ---- You may notice that there's no `()` after the `run` method call. In Perl 6, these are optional if you're not supplying any arguments any way. === Binding an action to the button Now that we have a visible window, it's time to make the button perform an action. The action we want to execute is to take the values from the two inputs, roll the correct number of dice with the correct number of sides, and present it to the user. The base code for binding an action to a button is to call `.clicked.tap` on it, and provide it with a code block. This code will be executed whenever the button is clicked. [source,perl6] ---- $button-roll.clicked.tap: { }; ---- You see we can also invoke a method using `:`, and then supplying its arguments. This saves you the trouble of having to add additional `( )` around the call, and in this case it would be annoying to have to deal with yet another set of parens. Next, we give the code block something to actually perform: [source,perl6] ---- $button-roll.clicked.tap: { CATCH { $label-result.text = "Can't roll with those numbers"; } X::TypeCheck::Binding::Parameter.new.throw if $entry-dice.text.Int < 1; $label-result.text = ($entry-dice.text.Int × roll($entry-sides.text.Int)).Str; }; ---- There's some new things in this block of code, so let's go over these. - `CATCH` is the block in which we'll end up if an exception is thrown in this scope. `roll` will throw an exception if the parameters are wrong, and this allows us to cleanly deal with that. - `X::TypeCheck::Binding::Parameter.new.throw` throws a new exception of type `X::TypeCheck::Binding::Parameter`. This is the same exception type as thrown by `roll` if something is wrong. We need to check the number of dice manually here, since `roll` doesn't take care of it, nor does any signature impose any restrictions on the value of the entry box. - `if` behind another statement. This is something Perl 6 allows, and in some circumstances can result in cleaner code. It's used here because it improves the readability of the code, and to show that it's possible. == The completed product And with that, you should have a dice roller in Perl 6, with both a console and GTK interface. Below you can find the complete, finished sourcefiles which you should have by now. === t/basic.t [source,perl6] ---- #! /usr/bin/env perl6 use v6.c; use Test; use Local::App::Dicer; plan 2; subtest "Legal rolls", { plan 50; for 1..50 { ok 1 ≤ roll($_) ≤ $_, "Rolls between 1 and $_"; } } subtest "Illegal rolls", { plan 3; throws-like { roll(0) }, X::TypeCheck::Binding::Parameter, "Zero is not accepted"; throws-like { roll(-1) }, X::TypeCheck::Binding::Parameter, "Negative rolls are not accepted"; throws-like { roll(1.5) }, X::TypeCheck::Binding::Parameter, "Can't roll half sides"; } done-testing; # vim: ft=perl6 ---- === lib/Local/App/Dicer.pm6 [source,perl6] ---- #! /usr/bin/env false use v6.c; unit module Local::App::Dicer; sub roll(Int:D $sides where $sides > 0) is export { $sides.rand.ceiling; } ---- === bin/dicer [source,perl6] ---- #! /usr/bin/env perl6 use v6.c; use Local::App::Dicer; use GTK::Simple; use GTK::Simple::App; multi sub MAIN(Int:D $dice, Int:D $sides where { $dice > 0 && $sides > 0 }) { say $dice × roll($sides) } multi sub MAIN(Bool:D :$gtk where $gtk == True) { my GTK::Simple::App $app .= new(title => "Dicer"); my GTK::Simple::Label $label-dice .= new(text => "Number of dice"); my GTK::Simple::Label $label-sides .= new(text => "Number of sides per die"); my GTK::Simple::Label $label-result .= new(text => ""); my GTK::Simple::Entry $entry-dice .= new(text => 0); my GTK::Simple::Entry $entry-sides .= new(text => 0); my GTK::Simple::Button $button-roll .= new(label => "Roll!"); $app.set-content( GTK::Simple::Grid.new( [0, 0, 1, 1] => $label-dice, [1, 0, 1, 1] => $entry-dice, [0, 1, 1, 1] => $label-sides, [1, 1, 1, 1] => $entry-sides, [0, 2, 2, 1] => $button-roll, [0, 3, 2, 1] => $label-result, ) ); $button-roll.clicked.tap: { CATCH { $label-result.text = "Can't roll with those numbers"; } X::TypeCheck::Binding::Parameter.new.throw if $entry-dice.text.Int < 1; $label-result.text = ($entry-dice.text.Int × roll($entry-sides.text.Int)).Str; }; $app.border-width = 20; $app.run; } sub USAGE { say "Launch Dicer as a GUI with --gtk, or supply two positive, round numbers as arguments."; } ---- == Installing your module Now that you have a finished application, you probably want to install it as well, so you can run it by calling `dicer` in your shell. For this, we'll be using `zef`. To install a local module, tell `zef` to try and install the local directory you're in: [source] ---- $ zef install . ---- This will resolve the dependencies of the local module, and then install it. You should now be able to run `dicer` from anywhere. [WARNING] ==== With most shells, you have to "rehash" your `$PATH` as well. On `bash`, this is done with `hash -r`, on `zsh` it's `rehash`. If you're using any other shell, please consult the manual. ====