From c455896ae9e69e2498742ff795e7886dee1ffa23 Mon Sep 17 00:00:00 2001 From: Patrick Spek Date: Fri, 5 Feb 2021 09:55:25 +0100 Subject: Move source files into src --- ...l6-introduction-to-application-programming.adoc | 784 --------------------- 1 file changed, 784 deletions(-) delete mode 100644 _posts/2018-03-20-perl6-introduction-to-application-programming.adoc (limited to '_posts/2018-03-20-perl6-introduction-to-application-programming.adoc') diff --git a/_posts/2018-03-20-perl6-introduction-to-application-programming.adoc b/_posts/2018-03-20-perl6-introduction-to-application-programming.adoc deleted file mode 100644 index fc00bd3..0000000 --- a/_posts/2018-03-20-perl6-introduction-to-application-programming.adoc +++ /dev/null @@ -1,784 +0,0 @@ ---- -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. -==== -- cgit v1.1