summaryrefslogtreecommitdiff
path: root/src/_posts/2018-03-20-perl6-introduction-to-application-programming.adoc
blob: fc00bd394045189ba510dbdd2e3a45879154a970 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
---
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 <dice> <sides>
----

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