この文書はプログラミング言語Rakuの全体像を素早くつかんでもらうことを目的として書かれたものです。
まだRakuを触ったことのない読者の方々が、ここからRakuをはじめてもらうことを狙いとしています。
この文書のいくつかの節では Rakuの公式文書 における(もっと完成されていて正確な)箇所を参照しています。
特定の事項に関してもっと情報がほしいのであればそれらの箇所を読んでみることをおすすめします。
この文書の中では、ほとんどのトピックにおいて例がふんだんに用いられています。 理解を深めるために、自分でもすべての例を再現してみることをおすすめします。
This work is licensed under the Creative Commons Attribution-ShareAlike 4.0 International License. To view a copy of this license, visit
もしこの文書に貢献したいのなら次のURLへ向かってください:
フィードバックは大歓迎です!:
もし、このRaku入門を気に入ったのなら、 Star を押していただければ幸いです。 Github.
1. イントロダクション
1.1. Raku とは
Rakuは高水準、汎用、漸進的型付けの言語です。 Rakuはマルチパラダイム言語です。手続き型、オブジェクト指向、関数型プログラミングをサポートしています。
-
TMTOWTDI (ティムトゥディ と発音します): やり方はひとつじゃない
1.2. 専門用語
-
Raku : はテストスイートもあわせての言語の仕様です。 仕様に基づいたテストスイートを通るような実装はRakuと考えられます。
-
Rakudo : はRakuのためのコンパイラです。
-
Zef : はRakuのモジュールのインストーラーです。
-
Rakudo Star: はRakudo, Zef, Rakuのモジュールのコレクション, 文書を含んだバンドルソフトです。
1.3. Rakuのインストール
Rakudo Starをインストールするには、次のコマンドをターミナルで実行してください:
mkdir ~/rakudo && cd $_
curl -LJO https://rakudo.org/latest/star/src
tar -xzf rakudo-star-*.tar.gz
mv rakudo-star-*/* .
rm -fr rakudo-star-*
./bin/rstar install
echo "export PATH=$(pwd)/bin/:$(pwd)/share/perl6/site/bin:$(pwd)/share/perl6/vendor/bin:$(pwd)/share/perl6/core/bin:\$PATH" >> ~/.bashrc
source ~/.bashrc
他のインストール方法を知りたければ次のリンクを参照してください。 https://rakudo.org/star/source
四つの選択肢から選んでください:
-
Linuxにおけるインストール手順と同じステップを踏む
-
homebrewによるインストール:
brew install rakudo-star
-
MacPortsによるインストール:
sudo port install rakudo
-
次のURLから最新のインストーラー(.dmg拡張子のついたファイル)をダウンロードする https://rakudo.perl6.org/downloads/star/
-
64bit版の場合: 最新のインストーラー(.msi拡張子のついたファイル)を次からダウンロードする https://rakudo.org/latest/star/win
-
インストール後
C:\rakudo\bin
がPATH変数に含まれていることを確認してください。
-
次のコマンドで公式のDockerイメージを入手してください
docker pull rakudo-star
-
イメージを含んだコンテナを実行するために次のコマンドを打ってください
docker run -it rakudo-star
1.4. Rakuのコードを実行する
Rakuのコードの実行はREPL (Read-Eval-Print Loop)を用いることによって行うことができます。
この実行を行うために、ターミナルを開き、ターミナルの窓に向かって perl6
と打ち、[Enter]ボタンを押してください。
そうすると、コマンドプロンプトから >
が表示されるはずです。
次に、コードの行を打って[Enter]を押してください。
REPL はこの行の値を出力するでしょう。
そうしたら、次のコードの行を打つか、 exit
と打った後[Enter]を押すことでREPLを去るか、どちらでも選ぶことができます。
あるいは、ファイルの中にコードを書いて、保存して実行してください。
Rakuのスクリプトは .raku
拡張子を持つことが推奨されています。
ターミナルの窓に対して perl6 filename.raku
と打ち、[Enter]を押してください。
REPLとは違って、それぞれの行の結果が自動的に出力されるでしょう。: 出力を行うには、 say
のような命令が含まれている必要があります。
REPLは多くの場合、特定のコードを実行するために用いられ、そのコードは一般的には一行です。 一行以上のプログラムに対しては、ファイルに保存してから実行することをおすすめします。
一行のコードはコマンドラインで非対話的に実行することもできます。
perl6 -e 'あなたの書いたコード'
と打ち [Enter]を押してください.
Rakudo StarはREPLを使い倒すためのラインエディタをバンドルしています。 もしRakudo Starではなく無印のRakudoをインストールしたのなら、行編集機能を有効化(履歴閲覧のための上矢印キーと下矢印キーの使用、入力編集のための左矢印キーと右矢印キーの使用、タブによる補完機能)していないはずです。 次のコマンドを実行してこれらの機能を有効化することを考えてみてください。:
|
1.5. エディタ
ほとんどの場合、Rakuのプログラムを書いて保存することになります。 そのため、Rakuの文法を認識できるまともなテキストエディタを持っているべきです。
私が個人的に使っていておすすめのエディタは Atom です。 モダンなテキストエディタであり、革新的なRakuのシンタックスハイライティング機能を持っています。 別の選択肢として、 Perl 6 FE というAtomのためのシンタックスハイライターがあります。 オリジナルのパッケージから派生したものですが、多くのバグフィックスと追加機能を含んでいます。
最近のバージョンのVimは、はじめから革新的なシンタックスハイライティング機能を持っています。 EmacsとPadreは追加のパッケージのインストールが必要になるでしょう。
1.6. こんにちは世界!
おなじみの こんにちは世界
の儀式をはじめましょう。
say 'こんにちは世界';
これはこういう風に書くこともできます:
'こんにちは世界'.say;
1.7. 文法の概要
Raku は 自由形式: ほとんどの場合、空白文字をどれだけの量つかっても良いです。ただし、空白文字が意味を持つ場合もあります。
命令文 は一般的にはコードの論理的な行です。最後にセミコロンがついている必要があります:
say "Hello" if True;
式 は値を返すような特殊なタイプの命令文です:
1+2
は 3
を返すでしょう。
式は 項 と 演算子 でできています。
項 は:
-
変数: 操作したり変更したりできる値です。
-
リテラル: 数や文字列のような定数です。
演算子 は次のような種類に分類されます。:
種類 |
説明 |
例 |
接頭辞 |
項の前 |
|
接中辞 |
項の間 |
|
接尾辞 |
項の後ろ |
|
接周辞 |
項の周り |
|
後置接周辞 |
項の後ろの、また別の項の周り |
|
1.7.1. 識別子
識別子とは項を定義したときに与えられる名前のことです。
-
アルファベットかアンダースコアで始まっていなければならない。
-
数字をふくむことができる。(ただし先頭文字は除く)
-
アルファベットがダッシュやアポストロフィ(ただし最初と最後の文字は除く)の右側にあるなら、ダッシュやアポストロフィをふくむことができる。
正しい例 |
間違った例 |
|
|
|
|
|
|
|
|
|
|
-
キャメルケース:
variableNo1
-
ケバブケース:
variable-no1
-
スネークケース:
variable_no1
識別子には好きなように名前をつけることができます。しかし、一貫して一つの命名規則を適用していくのがグッドプラクティスです。
ちゃんと意味のある名前をつければプログラミング人生を楽なものにしてくれるかもしれません。
-
var1 = var2 * var3
は文法的には正しいですが目的が明白ではありません。 -
monthly-salary = daily-rate * working-days
のほうが変数名としてふさわしいでしょう。
1.7.2. コメント
コメントはコンパイラーに無視され注釈として使われるテキストです。
コメントは三つのタイプに分けられます:
-
一行:
# これは一行のコメントです
-
埋め込み:
say #`(これは埋めこまれたコメントです) "Hello World."
-
複数行:
=begin comment これは複数行のコメントです。 コメント1 コメント2 =end comment
1.7.3. クォート
文字列はダブルクォートかシングルクォートのどちらかで囲まれていなければなりません。
下記に該当する場合は常にダブルクォートを使うべきです:
-
文字列がアポストロフィを含んでいる
-
文字列が展開される必要のある変数を含んでいる
say 'Hello World'; # Hello World
say "Hello World"; # Hello World
say "Don't"; # Don't
my $name = 'John Doe';
say 'Hello $name'; # Hello $name
say "Hello $name"; # Hello John Doe
2. 演算子
2.1. 一般的な演算子
下記の表は最も一般的に使われている演算子を掲載しています。
演算子 | 種類 | 説明 | 例 | 結果 |
---|---|---|---|---|
|
|
加算 |
|
|
|
|
減算 |
|
|
|
|
乗算 |
|
|
|
|
冪乗 |
|
|
|
|
除算 |
|
|
|
|
整数除算 (切り捨て) |
|
|
|
|
法 |
|
|
|
|
割り切れるか否か |
|
|
|
|
|||
|
|
最大公約数 |
|
|
|
|
最小公倍数 |
|
|
|
|
数値が等しい |
|
|
|
|
数値が等しくない |
|
|
|
|
数値が小さい |
|
|
|
|
数値が大きい |
|
|
|
|
数値が等しいか小さい |
|
|
|
|
数値が等しいか大きい |
|
|
|
|
数値の三元演算子 |
|
|
|
|
|||
|
|
|||
|
|
文字列が等しい |
|
|
|
|
文字列が等しくない |
|
|
|
|
辞書順で小さい |
|
|
|
|
辞書順で大きい |
|
|
|
|
辞書順で小さいか等しい |
|
|
|
|
辞書順で大きいか等しい |
|
|
|
|
文字列の三元演算子 |
|
|
|
|
|||
|
|
|||
|
|
スマート三元演算子 |
|
|
|
|
|||
|
|
代入 |
|
|
|
|
文字列の結合 |
|
|
|
|
|||
|
|
文字列の複製 |
|
|
|
|
|||
|
|
スマートマッチ |
|
|
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
インクリメント |
|
|
|
インクリメント |
|
|
|
|
|
デクリメント |
|
|
|
デクリメント |
|
|
|
|
|
被演算子を数値にする |
|
|
|
|
|||
|
|
|||
|
|
被演算子を数値にし、その負の値を返す |
|
|
|
|
|||
|
|
|||
|
|
被演算子をブーリアンにする |
|
|
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
被演算子をブーリアンにし、その否定を返す |
|
|
|
|
Rangeクラスのコンストラクタ |
|
|
|
|
Rangeクラスのコンストラクタ |
|
|
|
|
Rangeクラスのコンストラクタ |
|
|
|
|
Rangeクラスのコンストラクタ |
|
|
|
|
Rangeクラスのコンストラクタ |
|
|
|
|
遅延リストのコンストラクタ |
|
|
|
|
平坦化 |
|
|
|
|
2.2. 逆転演算子
演算子の前に R
を追加することで被演算子を逆転させる効果を持たせることができるでしょう。
通常の演算 | 結果 | 逆転演算子 | 結果 |
---|---|---|---|
|
|
|
|
|
|
|
|
2.3. 簡約演算子
簡約演算子は値のリストに対して作用します。
簡約演算子は演算子を角括弧 []
で囲むことで構成されます。
通常の演算 | 結果 | 簡約演算子 | 結果 |
---|---|---|---|
|
|
|
|
|
|
|
|
もしその演算の優先順位を含んだ、演算子のすべてのリストを知りたいのであれば、次のURLを参照することをすすめます https://docs.raku.org/language/operators |
3. 変数
Rakuの変数は三つのカテゴリに分類されます: スカラ、配列、ハッシュです。
シジル (ラテン語で印という意味) は変数を分類するときに使われる接頭辞です。
-
$
はスカラのために使われます -
@
は配列のために使われます -
%
はハッシュのために使われます
3.1. スカラ
スカラはある値や参照を持っています。
# 文字列
my $name = 'John Doe';
say $name;
# 整数
my $age = 99;
say $age;
あるスカラに対して行うことのできる演算の種類は、そのスカラが保持している値に依存しています。
my $name = 'John Doe';
say $name.uc;
say $name.chars;
say $name.flip;
JOHN DOE
8
eoD nhoJ
もし文字列に対して適用できるすべてのメソッドのリストを知りたいのであれば、次のURLを参照することをすすめます https://docs.raku.org/type/Str |
my $age = 17;
say $age.is-prime;
True
もし整数に対して適用できるすべてのメソッドのリストを知りたいのであれば、次のURLを参照することをすすめます https://docs.raku.org/type/Int |
my $age = 2.3;
say $age.numerator;
say $age.denominator;
say $age.nude;
23
10
(23 10)
もし有理数に対して適用できるすべてのメソッドのリストを知りたいのであれば、次のURLを参照することをすすめます https://docs.raku.org/type/Rat |
3.2. 配列
配列は複数の値を含んだリストです。
my @animals = 'camel','llama','owl';
say @animals;
下記の例のように、配列に対してたくさんの演算をおこなうことが可能です:
チルダ ~ は文字列の結合のために使われています。
|
スクリプト
my @animals = 'camel','vicuña','llama';
say "The zoo contains " ~ @animals.elems ~ " animals";
say "The animals are: " ~ @animals;
say "I will adopt an owl for the zoo";
@animals.push("owl");
say "Now my zoo has: " ~ @animals;
say "The first animal we adopted was the " ~ @animals[0];
@animals.pop;
say "Unfortunately the owl got away and we're left with: " ~ @animals;
say "We're closing the zoo and keeping one animal only";
say "We're going to let go: " ~ @animals.splice(1,2) ~ " and keep the " ~ @animals;
出力
The zoo contains 3 animals
The animals are: camel vicuña llama
I will adopt an owl for the zoo
Now my zoo has: camel vicuña llama owl
The first animal we adopted was the camel
Unfortunately the owl got away and we're left with: camel vicuña llama
We're closing the zoo and keeping one animal only
We're going to let go: vicuña llama and keep the camel
.elems
は配列の中の要素数を返します。
.push()
は配列に一つかそれ以上の要素を追加します。
配列の中の特定の要素の位置を指定することで、その要素にアクセスすることができます。 @animals[0]
.pop
は配列から最後の要素を削除し、そしてそれを返します。
.splice(a,b)
は位置 a
から始まる b
個の要素を削除します。
3.2.1. 固定サイズの配列
基本的な配列は次のように宣言されます:
my @array;
基本的な配列は不定の長さを持つことができ、それゆえにこの機能は自動拡張とよばれています。
この配列は要素数に制限がありません。
対照的に、固定サイズの配列をつくることもできます。
あらかじめ定義されたサイズを超えたところにアクセスすることはできません。
固定サイズの配列を宣言するためには、名前のすぐ後の角括弧の中にその最大要素数を指定してください。:
my @array[3];
この配列は三つの値を保持することができ、その添え字は0から2までの値をとります。
my @array[3];
@array[0] = "first value";
@array[1] = "second value";
@array[2] = "third value";
四つ目の値をこの配列に対して追加することはできません。:
my @array[3];
@array[0] = "first value";
@array[1] = "second value";
@array[2] = "third value";
@array[3] = "fourth value";
Index 3 for dimension 1 out of range (must be 0..2)
3.2.2. 多次元配列
今まで見てきた配列は一次元配列でした。
幸運なことに、Rakuでは多次元配列を定義することができます。
my @tbl[3;2];
この配列は二次元です。 一つ目の次元は最大で三つの値をもつことができ、二つ目の次元は最大で二つの値を持つことができます。
3x2の格子だと考えてください。
my @tbl[3;2];
@tbl[0;0] = 1;
@tbl[0;1] = "x";
@tbl[1;0] = 2;
@tbl[1;1] = "y";
@tbl[2;0] = 3;
@tbl[2;1] = "z";
say @tbl
[[1 x] [2 y] [3 z]]
[1 x]
[2 y]
[3 z]
もし配列に関するすべての情報を知りたいのであれば、次のURLを参照することをすすめます https://docs.raku.org/type/Array |
3.3. ハッシュ
my %capitals = 'UK','London','Germany','Berlin';
say %capitals;
my %capitals = UK => 'London', Germany => 'Berlin';
say %capitals;
ハッシュに対して呼び出すことのできるいくつかのメソッド:
スクリプト
my %capitals = UK => 'London', Germany => 'Berlin';
%capitals.push: (France => 'Paris');
say %capitals.kv;
say %capitals.keys;
say %capitals.values;
say "The capital of France is: " ~ %capitals<France>;
出力
(France Paris Germany Berlin UK London)
(France Germany UK)
(Paris Berlin London)
The capital of France is: Paris
.push: (key => 'Value')
は新たな キー/値のペアを追加します。
.kv
はすべてのキーと値を含んだリストを返します。
.keys
はすべてのキーを含んだリストを返します。
.values
はすべての値を含んだリストを返します。
キーを指定することでハッシュの中の特定の値にアクセスすることができます。%hash<key>
もしハッシュに関するすべての情報を知りたいのであれば、次のURLを参照することをすすめます https://docs.raku.org/type/Hash |
3.4. 型
今までの例では、変数が保持しているべき値の型について指定してはいませんでした。
.WHAT は変数が保持している値の型を返します。
|
my $var = 'Text';
say $var;
say $var.WHAT;
$var = 123;
say $var;
say $var.WHAT;
上記の例からわかるように、 $var
の中の値の型は一度 (Str) になり、それから (Int) になっています。
このプログラミングのスタイルは動的型付けと呼ばれています。 変数はAny型の値を持つことができるという意味で動的なのです。
では、下記の例を実行してみましょう:
変数名の前の Int
に注目してください。
my Int $var = 'Text';
say $var;
say $var.WHAT;
これは実行に失敗して次のようなメッセージを返すでしょう: Type check failed in assignment to $var; expected Int but got Str
あらかじめ変数の型は(Int)でなければならないと指定したのが原因です。 この変数に対して(Str)型の値を代入しようとしたときに、失敗してしまいました。
このプログラミングのスタイルは静的型付けとよばれています。 変数の型は代入の前に定義され、変えることができないという意味で静的なのです。
Rakuは 漸進的型付け に分類されます; 静的 型付けと 動的 型付けの両方を使うことができるのです。
my Int @array = 1,2,3;
say @array;
say @array.WHAT;
my Str @multilingual = "Hello","Salut","Hallo","您好","안녕하세요","こんにちは";
say @multilingual;
say @multilingual.WHAT;
my Str %capitals = UK => 'London', Germany => 'Berlin';
say %capitals;
say %capitals.WHAT;
my Int %country-codes = UK => 44, Germany => 49;
say %country-codes;
say %country-codes.WHAT;
最初の二つの型は使わないかもしれませんが情報提供のために掲載しておきます。
|
|
|
|
|
|
||
|
|
||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3.5. イントロスペクション
イントロスペクションはその型といったオブジェクトのプロパティについて情報を得るための処理です。
前節の例の一つでは変数の型を返すために .WHAT
を使いました。
my Int $var;
say $var.WHAT; # (Int)
my $var2;
say $var2.WHAT; # (Any)
$var2 = 1;
say $var2.WHAT; # (Int)
$var2 = "Hello";
say $var2.WHAT; # (Str)
$var2 = True;
say $var2.WHAT; # (Bool)
$var2 = Nil;
say $var2.WHAT; # (Any)
値を持っている変数の型はその値と相互に関連があります。
型の指定された空の変数の型はその変数が宣言されたときの型です。
型の指定されていない空の変数の型は (Any)
です。
変数の値をクリアするためには Nil
を代入してください。
3.6. スコーピング
初めに変数を使う前に、宣言されている必要があります。
様々な宣言子がRakuでは使われています。今までのところ、使ってきたのは my
です。
my $var=1;
my
宣言子は変数に対して レキシカル スコープを与えます。
つまり、その変数はそれが宣言されたのと同じブロックの中でしかアクセスできなくなります。
Rakuのブロックは { }
で囲まれます。
もしブロックがみつからないのなら、その変数はRakuのスクリプト全体で使える状態になっています。
{
my Str $var = 'Text';
say $var; # はアクセス可能です
}
say $var; # はアクセス可能ではありません。エラーを返します。
変数はその変数が定義されたブロックの中でのみアクセスが可能なので、別のブロックでは同じ変数名で使用できます。
{
my Str $var = 'Text';
say $var;
}
my Int $var = 123;
say $var;
3.7. 代入と束縛
前節の例では、どうやって変数に値を 代入 するかをみてきました。
代入 は =
演算子を用いて行われます。
my Int $var = 123;
say $var;
変数に代入された値は変更することができます:
my Int $var = 123;
say $var;
$var = 999;
say $var;
出力
123
999
一方、変数に 束縛 された値は変えることはできません。
束縛 は :=
演算子を用いて行われます。
my Int $var := 123;
say $var;
$var = 999;
say $var;
出力
123
Cannot assign to an immutable value
my $a;
my $b;
$b := $a;
$a = 7;
say $b;
$b = 8;
say $a;
出力
7
8
束縛変数は双方向性をもっています。
$a := $b
と $b := $a
は同じ効果を持っています。
もし変数に関する情報をもっと知りたいのであれば、次のURLを参照することをすすめます https://docs.raku.org/type/variables |
4. 関数とミューテータ
関数とミューテータを区別することは重要です。
関数はそれが呼ばれたときのオブジェクトの状態を変更しません。
ミューテータはオブジェクトの状態を変更します。
スクリプト
1
2
3
4
5
6
7
8
9
10
my @numbers = [7,2,4,9,11,3];
@numbers.push(99);
say @numbers; #1
say @numbers.sort; #2
say @numbers; #3
@numbers.=sort;
say @numbers; #4
出力
[7 2 4 9 11 3 99] #1
(2 3 4 7 9 11 99) #2
[7 2 4 9 11 3 99] #3
[2 3 4 7 9 11 99] #4
.push
はミューテータです。配列の状態を変更します。 (#1)
.sort
は関数です。ソートされた配列を返しますが、配列の初期状態を変更するわけではありません。:
-
(#2) はソートされた配列が返されたことを示しています。
-
(#3) は元々の配列は変更されていないことを示しています。
関数にミューテータとしてふるまうように強制するために .
のかわりに .=
を使っています。(#4) (スクリプトの9行目)
5. ループと条件文
Rakuは多数の条件文とループ構文を持っています。
5.1. if
条件が満たされた時、つまり式が True
と評価された時だけコードが実行されます。
my $age = 19;
if $age > 18 {
say 'Welcome'
}
Rakuでは、コードと条件は反転することができます。
コードと条件が反転されていたとしても、いつも先に条件が評価されます。
my $age = 19;
say 'Welcome' if $age > 18;
もし条件が満たされないなら、下記を使って、別のブロックを実行するように指定することができます:
-
else
-
elsif
# 変数の値を変えて同じコードを実行
my $number-of-seats = 9;
if $number-of-seats <= 5 {
say 'I am a sedan'
} elsif $number-of-seats <= 7 {
say 'I am 7 seater'
} else {
say 'I am a van'
}
5.2. unless
unless
を使えば、if命令の否定版を書くことができます。
次のコード:
my $clean-shoes = False;
if not $clean-shoes {
say 'Clean your shoes'
}
は次のように書くことができます:
my $clean-shoes = False;
unless $clean-shoes {
say 'Clean your shoes'
}
Rakuでの否定は !
か not
を使って行われます。
unless (condition)
は if not (condition)
のかわりに用いられます。
unless
は else
節を持つことができません。
5.3. with
with
は if
命令のようにふるまいます。しかし変数が定義されているかどうかを調べます。
my Int $var=1;
with $var {
say 'Hello'
}
変数に対して値を代入しないでコードを実行した場合何も起こらないでしょう。
my Int $var;
with $var {
say 'Hello'
}
without
は with
の否定版です。 unless
と関連づけて覚えておくとよいでしょう。
もしはじめの with
条件が満たされないなら、 orwith
を使って代替となるパスを指定することができます。
with
と orwith
の関係性は if
と elsif
の関係性にたとえることができます。
5.4. for
for
ループは複数の値に対する反復処理を行うことができます。
my @array = 1,2,3;
for @array -> $array-item {
say $array-item * 100
}
反復変数 $array-item
を作り、配列のそれぞれの要素に対して *100
の操作を行ったことに注目してください。
5.5. given
given
は他の言語においてswitch命令と呼ばれているものと等価なRakuの命令です。
しかし、他の言語よりも強力です。
my $var = 42;
given $var {
when 0..50 { say 'Less than or equal to 50'}
when Int { say "is an Int" }
when 42 { say 42 }
default { say "huh?" }
}
条件が満たされると、条件が満たされるかどうか調べる処理は止まります。
別の選択肢として、 proceed
を使うと、Rakuは条件が満たされた後も、この調べる処理の実行を続けます。
my $var = 42;
given $var {
when 0..50 { say 'Less than or equal to 50';proceed}
when Int { say "is an Int";proceed}
when 42 { say 42 }
default { say "huh?" }
}
5.6. loop
loop
は for
を書くための別の選択肢です。
実際に、 loop
はC言語族において書かれる for
と同じです。
RakuはC言語族の一員なのです。
loop (my $i = 0; $i < 5; $i++) {
say "The current number is $i"
}
もしループ構文と条件文に関する情報をもっと知りたいのであれば、次のURLを参照することをすすめます https://docs.raku.org/language/control |
6. I/O
Rakuにおいて、二つの最も一般的な 入力/出力 のインタフェースは ターミナル と ファイル です。
6.1. ターミナルを用いた基本的なI/O
6.1.1. say
say
は標準出力に対する書き込みを行います。その最後に改行を付加します。つまり、次のようなコードは:
say 'Hello Mam.';
say 'Hello Sir.';
二つの行に分かれて書き込まれることになります。
6.1.2. print
一方、 print
は say
のようにふるまいますが、改行を付加しないという違いがあります。
say
を print
で置き換えてみて結果を比べてみましょう。
6.1.3. get
get
はターミナルからの入力を取得するために使われます。
my $name;
say "Hi, what's your name?";
$name = get;
say "Dear $name welcome to Raku";
上記のコードが実行されると、ターミナルは、あなたが名前を入力するのを待つようになります。名前を打って [Enter] を押しましょう。 その後、あいさつをしてくれるでしょう。
6.1.4. prompt
prompt
は print
と get
の組み合わせです。
上の例は次のように書くことができます:
my $name = prompt "Hi, what's your name? ";
say "Dear $name welcome to Raku";
6.2. シェルコマンドの実行
二つのサブルーチンをシェルコマンドを実行するために使うことができます:
-
run
シェルを介在せずに外部コマンドを実行します -
shell
システムシェルを通じてコマンドを実行します。プラットフォームとシェル依存です。 すべてのシェルのメタ文字はシェルによって解釈されます。これには、パイプ、リダイレクト、ユーザー環境変数などが含まれます。
my $name = 'Neo';
run 'echo', "hello $name";
shell "ls";
shell "dir";
echo
と ls
はLinuxにおける一般的なシェルのキーワードです。:
echo
はターミナルにテキストを出力します。 (Rakuにおける say
と等価です。)
ls
はカレントディレクトリのすべてのファイルとフォルダを表示します。
dir
はWindowsにおける ls
と等価なキーワードです。
6.3. ファイル I/O
6.3.1. slurp
slurp
はファイルからデータを読み込むために使われます。
次のような内容のテキストファイルを作ってください:
John 9
Johnnie 7
Jane 8
Joanna 7
my $data = slurp "datafile.txt";
say $data;
6.3.2. spurt
spurt
はデータをファイルに書き込むために使われます。
my $newdata = "New scores:
Paul 10
Paulie 9
Paulo 11";
spurt "newdatafile.txt", $newdata;
上記のコードの実行後、 newdatafile.txt という名前の新しいファイルが作られるはずです。 このファイルは新しいスコアを含んでいるでしょう。
6.4. ファイルとディレクトリの操作
Rakuはシェルコマンドに頼らなくてもディレクトリの内容を表示することができます( シェルコマンドの例としては ls
が挙げられます )。
say dir; # カレントディレクトリのファイルとフォルダを表示する
say dir "/Documents"; # 指定されたディレクトリのファイルとフォルダを表示する
加えて、新しいディレクトリを作ったり削除したりすることもできます。
mkdir "newfolder";
rmdir "newfolder";
mkdir
は新しいディレクトリをつくります
rmdir
は空のディレクトリを削除し、もし空でないのならエラーを返します。
ファイルかディレクトリであれば、パスが存在するかどうか確かめることもできます。:
下記のスクリプトを実行するディレクトリで、 folder123
という空のフォルダと script123.raku
という空のrakuファイルを生成してください。
say "script123.raku".IO.e;
say "folder123".IO.e;
say "script123.raku".IO.d;
say "folder123".IO.d;
say "script123.raku".IO.f;
say "folder123".IO.f;
IO.e
はディレクトリ/ファイルが存在するかどうか調べます。
IO.f
はパスがファイルかどうか調べます。
IO.d
はパスがディレクトリかどうか調べます。
Windowsのユーザーはディレクトリを定義するために / か \\ を使うことができますC:\\rakudo\\bin C:/rakudo/bin |
もしI/Oに関する情報をもっと知りたいのであれば、次のURLを参照することをすすめます https://docs.raku.org/type/IO |
7. サブルーチン
7.1. 定義
サブルーチン (サブ や 関数 とも呼ばれます) は機能のパッケージングと再利用の手段です
サブルーチンの定義は sub
というキーワードから始まります。定義の後、つけた名前を使って呼び出すことができます。
下記の例をよく見てください:
sub alien-greeting {
say "Hello earthlings";
}
alien-greeting;
先ほどの例では、入力を必要としないサブルーチンを紹介しました。
7.2. シグネチャ
サブルーチンは入力を必要とする場合があります。この時の入力は 引数 によって与えられます。 サブルーチンはゼロかそれ以上の パラメータ を定義します。 サブルーチンが定義するパラメータの数と型は シグネチャ と呼ばれています。
下記のサブルーチンは引数として文字列を受け取っています。
sub say-hello (Str $name) {
say "Hello " ~ $name ~ "!!!!"
}
say-hello "Paul";
say-hello "Paula";
7.3. 多重ディスパッチ
同じ名前を持っているが異なったシグネチャを持つように複数のサブルーチンを定義することができます。
サブルーチンが呼ばれると、ランタイム環境は与えられた引数の数と型をもとにしてどれを使うべきであるか決定します。
このタイプのサブルーチンは普通のサブルーチンと同じように定義できます。ただし、このとき sub
のかわりに multi
というキーワードを用います。
multi greet($name) {
say "Good morning $name";
}
multi greet($name, $title) {
say "Good morning $title $name";
}
greet "Johnnie";
greet "Laura","Mrs.";
7.4. デフォルトパラメータとオプションパラメータ
もしサブルーチンが一つの引数を受け取るように定義されていて、必要となる引数が与えられずに呼び出されたのなら、このサブルーチンの実行は失敗します。
Rakuでは次のような引数をともなったサブルーチンを定義することができます:
-
オプションパラメータ
-
デフォルトパラメータ
オプションパラメータはパラメータの名前に対して ?
を付加することで定義できます。
sub say-hello($name?) {
with $name { say "Hello " ~ $name }
else { say "Hello Human" }
}
say-hello;
say-hello("Laura");
もしユーザーが引数を与える必要がないのであれば、デフォルト値を定義することができます。
これは、サブルーチンの定義内でパラメータに対して値を代入することで行うことができます。
sub say-hello($name="Matt") {
say "Hello " ~ $name;
}
say-hello;
say-hello("Laura");
7.5. 値の返却
今まで見てきたすべてのサブルーチンは、ターミナルにテキストを出力するといった具合に 何かをするもの でした 。 でも時には、プログラムの後段の処理で利用できるような何らかの値を 返して もらうためにサブルーチンを実行することもあるでしょう。
もし関数がそのブロックの最後まで実行されるなら、最後の文や式が返り値を決定するでしょう。
sub squared ($x) {
$x ** 2;
}
say "7 squared is equal to " ~ squared(7);
コードが大きくなってしまったなら 明示的に 何を返そうとしているのかを指定することは良い対処法かもしれません。
return
キーワードを用いることでこれを行うことができます。
sub squared ($x) {
return $x ** 2;
}
say "7 squared is equal to " ~ squared(7);
7.5.1. 返り値の制限
以前の例の一つでは、どうやって引数の受け取る型をある型に制限するかについて見ました。 同じことを返り値でも行うことができます。
返り値をある型に制限するには、矢印記号 -->
をシグネチャで使ってください。
sub squared ($x --> Int) {
return $x ** 2;
}
say "1.2 squared is equal to " ~ squared(1.2);
もし、型の制限にマッチする返り値を渡しそびれてしまったなら、エラーが投げられるでしょう。
Type check failed for return value; expected Int but got Rat (1.44)
型の制限は返り値の型を指定できるだけではなく; 定義済みか否かも指定することができます。 以前の例では、返り値は
それはそれとして、これらの型の制限をつかうことはとても実用的です。
|
もしサブルーチンと関数に関する情報をもっと知りたいのであれば、次のURLを参照することをすすめます https://docs.raku.org/language/functions |
8. 関数型プログラミング
この章では関数型プログラミングを容易にしてくれるいくつかの機能を見ていこうと思います。
8.1. 関数は第一級オブジェクト
関数/サブルーチンは第一級オブジェクトです:
-
引数として用いることができます
-
別の関数から返すことができます
-
変数に代入することができます
この概念を説明するためのよい例は map
関数です。
map
は 高階関数 です。ほかの関数を引数として受け取ることができます。
my @array = <1 2 3 4 5>;
sub squared($x) {
$x ** 2
}
say map(&squared,@array);
(1 4 9 16 25)
まず squared
と呼ばれるサブルーチンを定義しました。このサブルーチンは引数として与えられた値を二乗します。
次に、高階関数である map
に対して、このサブルーチンと配列の二つの引数を与えます。
結果は、配列の各要素の平方のリストとなります。
引数としてサブルーチンを用いるときはその名前の前に &
をつけなければならないことに注意してください。
8.2. 無名関数
無名関数 は ラムダ とも呼ばれています。
無名関数は識別子に束縛されません。(名前をもっていないため)
map
の例を書き換えて無名関数を使うようにしましょう
my @array = <1 2 3 4 5>;
say map(-> $x {$x ** 2},@array);
サブルーチンを宣言して map
の引数として渡す代わりに、 map
の中で直接定義していることに注意してください。
無名関数の中で -> $x {$x ** 2}
のように定義しています。
Rakuではこのような使われ方の無名関数を ポインティブロック と呼びます。
my $squared = -> $x {
$x ** 2
}
say $squared(9);
8.3. チェイン
Rakuでは、メソッドはチェインすることができます。メソッドの結果を引数としてほかのメソッドに渡す必要はありません。
例題: 配列が与えられているとして、値の重複が無いように降順にソートしてください。
これはチェインを使わなかった場合の解答です:
my @array = <7 8 9 0 1 2 4 3 5 6 7 8 9>;
my @final-array = reverse(sort(unique(@array)));
say @final-array;
この例では @array
に対して unique
関数を呼び、その結果を sort
の引数として渡します。さらにそのソートした結果を reverse
に渡します。
対照的に、上記の例は メソッドチェイン を利用して、次のように書くことができます。:
my @array = <7 8 9 0 1 2 4 3 5 6 7 8 9>;
my @final-array = @array.unique.sort.reverse;
say @final-array;
メソッドチェインは 見た目が良い ということが一目瞭然ですね。
8.4. フィード演算子
フィード演算子 は、関数型言語では パイプ と呼ばれ、メソッドチェインをさらに分かりやすくしてくれます。
my @array = <7 8 9 0 1 2 4 3 5 6 7 8 9>;
@array ==> unique()
==> sort()
==> reverse()
==> my @final-array;
say @final-array;
まず `@array`で始まり 次に重複のないリストを返します
次にソートします
次にリバースします
次に結果を `@final-array` に格納します
メソッドの呼び出し順は、最初から最後のステップへのトップダウンであることに注目してください。
my @array = <7 8 9 0 1 2 4 3 5 6 7 8 9>;
my @final-array-v2 <== reverse()
<== sort()
<== unique()
<== @array;
say @final-array-v2;
後方フィードは前方フィードに似ていますが、順序が逆です。
メソッドの呼び出し順は、最後から最初のステップへのボトムアップであることに注目してください。
8.5. ハイパー演算子
ハイパー演算子 >>.
はリストの要素のすべてに対してメソッドの呼び出しを行い、その結果のリストを返します。
my @array = <0 1 2 3 4 5 6 7 8 9 10>;
sub is-even($var) { $var %% 2 };
say @array>>.is-prime;
say @array>>.&is-even;
ハイパー演算子を用いることでRakuに組み込まれているメソッドを呼び出すこともできます。例えば、is-prime
は数値が素数かそうでないかを判別する組み込みのメソッドです。
加えて、新しいサブルーチンを定義してハイパーオペレーターを使って呼び出すこともできます。この場合、 &
をメソッドの先頭に追加しなければなりません。 例えば、&is-even
といった具合です。
配列に対して反復処理を行うための for
によるループを書くことから脱却することができ、とても実用的です。
Rakuは元の値の並びと結果の値の並びが同じになることを保証します。 ただし、 Rakuがもとの並びと同じ順番や同じスレッドで実際にメソッドを呼び出しているという 保証はない です。 そのため、副作用を持つメソッド、例えば say や print には注意してください。
|
8.6. ジャンクション
ジャンクション は値の論理的な重ね合わせです。
下記の例では 1|2|3
がジャンクションです。
my $var = 2;
if $var == 1|2|3 {
say "The variable is 1 or 2 or 3"
}
ジャンクションの使用は通常は オートスレッディング のトリガーとなります。; この演算はジャンクションの要素それぞれに対して実行され、すべての結果を結合した新たなジャンクションが生成され、それが返されます。
8.7. 遅延リスト
遅延リスト は遅延評価されるリストです。
遅延評価とは、必要な時まで式の評価を遅らせ、ルックアップテーブルに結果を保存しておくことで不要な繰り返しの評価を避けるものです。
以下のような恩恵を含んでいます:
-
不要な計算を避けることでパフォーマンスが向上する
-
潜在的には無限のデータ構造をつくることができる
-
制御フローを定義することができる
遅延リストをつくるためには接中辞演算子 …
を用います。
遅延リストは、初期要素(複数可) 、ジェネレータ 、終点 を持っています。
my $lazylist = (1 ... 10);
say $lazylist;
初期要素は1、終点は10です。ジェネレータは定義されていないので、デフォルトのジェネレータは次の値(+1)です
つまり、この遅延リストは(もし要求されれば)、(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)という要素のリストを返すでしょう。
my $lazylist = (1 ... Inf);
say $lazylist;
この遅延リストは(もし要求されれば)1から無限までの間のすべての整数、つまり任意の整数を返します。
my $lazylist = (0,2 ... 10);
say $lazylist;
初期要素は0と2で終点は10です。
ジェネレータは定義されていませんが、初期要素を使ってRakuはジェネレータは(+2)であると演繹します。
この遅延リストは(もし要求されれば)次のような要素を返します。(0, 2, 4, 6, 8, 10)
my $lazylist = (0, { $_ + 3 } ... 12);
say $lazylist;
この例では、明示的に { }
で囲まれたジェネレータを定義しています。
この遅延リストは(もし要求されれば)次のような要素を返します。(0, 3, 6, 9, 12)
明示的なジェネレータを使うときは、終点はジェネレータが返すことのできるような値のひとつでなければなりません。 別の選択肢として、 これではジェネレータは止まりません
これならジェネレータは止まります
|
8.8. クロージャ
RakuのすべてのCode型のオブジェクトはクロージャです。これは外のスコープのレキシカル変数を参照できるということを意味しています。
sub generate-greeting {
my $name = "John Doe";
sub greeting {
say "Good Morning $name";
};
return &greeting;
}
my $generated = generate-greeting;
$generated();
もし上記のコードを動かしたのなら、 Good Morning John Doe
とターミナルに表示されたはずです。
非常に単純な結果でしたが、この例で面白いところは、中の greeting
というサブルーチンが、それが実行される前に外のサブルーチンから返されているということです。
$generated
は クロージャ になったのです。
クロージャ は二つのものを結びつけた特別な種類のオブジェクトです:
-
サブルーチン
-
サブルーチンの生成された環境
環境は、そのクロージャが生成されたときにスコープ内にあったすべてのローカル変数で構成されています。
この場合、 $generated
はサブルーチン greeting
と文字列 John Doe
の両方を結びつけたクロージャといえます。
では、もっと面白い例を見てみましょう。
sub greeting-generator($period) {
return sub ($name) {
return "Good $period $name"
}
}
my $morning = greeting-generator("Morning");
my $evening = greeting-generator("Evening");
say $morning("John");
say $evening("Jane");
この例では、$period
というひとつの引数を受け取って新しいサブルーチンを返すような、サブルーチン greeting-generator($period)
を定義しました。このサブルーチンが返す新しいサブルーチンは $name
という引数を受け取り、そして作成されたあいさつを返します。
基本的に、greeting-generator
はサブルーチンのファクトリです。この例では、我々は greeting-generator
を二つの新しいサブルーチンを生成するために用いました。
ひとつは Good Morning
とあいさつし、もうひとつは Good Evening
とあいさつします。
$morning
と $evening
は両方ともクロージャです。同じサブルーチン本体の定義を共有していますが、違う環境を保存しています。
$morning
の環境では $period
は Morning
です。
$evening
の環境では $period
は Evening
です。
9. クラスとオブジェクト
前章では、どうやってRakuが関数型プログラミングを楽にしてくれるかについて学びました。
この章ではRakuにおけるオブジェクト指向プログラミングについてみていきましょう。
9.1. イントロダクション
オブジェクト指向 プログラミングは昨今広く使われているパラダイムの一つです。
オブジェクト は一緒にバンドルされた変数やサブルーチンの集合です。
変数は 属性 と呼ばれ、サブルーチンは メソッド とよばれます。
属性はオブジェクトの 状態 を定義し、メソッドはオブジェクトの ふるまい を定義します。
クラス は オブジェクト を作るための鋳型です。
これらの関係を理解するために、下記の例を考えてみてください:
現在四人が部屋にいる |
オブジェクト ⇒ 4 人 |
四人は人間である |
クラス ⇒ 人間 |
四人はそれぞれ異なった名前、年齢、性別、国籍を持っている |
属性 ⇒ 名前、年齢、性別、国籍 |
オブジェクト指向 の用語では、これらのオブジェクトはクラスの インスタンス と呼ばれています。
下記のスクリプトについて考えてみてください:
class Human {
has $.name;
has $.age;
has $.sex;
has $.nationality;
}
my $john = Human.new(name => 'John', age => 23, sex => 'M', nationality => 'American');
say $john;
class
キーワードはクラスを定義するのに使われます。
has
キーワードはクラスの属性を定義するのに使われます。
.new()
メソッドは コンストラクタ と呼ばれるものです。そのメソッドの呼ばれたクラスのインスタンスとしてオブジェクトを生成します。
上記のスクリプトでは、新しい変数 $john
は、 Human.new()
によって定義された"Human"の新しいインスタンスを持っています。
クラスは my
を使うことで レキシカルスコープ とすることもできます:
my class Human {
}
9.2. カプセル化
カプセル化は、データとメソッドの集合を一緒にバンドルするというオブジェクト指向の概念です。
オブジェクト内のデータ(属性)は プライベート であるべきです。つまり、オブジェクトの中からしかアクセスできないようにするべきです。
オブジェクトの外から属性にアクセスするためには アクセッサ と呼ばれるメソッドを用います。
下記の二つのスクリプトは同じ結果を返します。
my $var = 7;
say $var;
my $var = 7;
sub sayvar {
$var;
}
say sayvar;
sayvar
メソッドはアクセッサです。変数に直接アクセスしなくても、変数の値にアクセスできるようにしてくれます。
Rakuでは トゥイジル の使用によって楽にカプセル化を行うことができます。
トゥイジルは補助的な シジル です。シジルと属性の名前の間に書きます。
二つのトゥイジルがクラスでは使われます:
-
!
は明示的に属性がプライベートであることを宣言するときに使います -
.
は属性のアクセッサを自動的に生成するときに使います
デフォルトでは、すべての属性はプライベートですが、いつも !
トゥイジルを使うことはよい習慣です。
したがって、上のクラスは次のように書き換えるべきです:
class Human {
has $!name;
has $!age;
has $!sex;
has $!nationality;
}
my $john = Human.new(name => 'John', age => 23, sex => 'M', nationality => 'American');
say $john;
次の命令をスクリプトに追加してみましょう: say $john.age;
このようなエラーが返ってくるはずです: Method 'age' not found for invocant of class 'Human'
$!age
はプライベートでありオブジェクト内でしか使えないというのが原因です。
オブジェクトの外からアクセスしようとするとエラーが返ります。
では、has $!age
を has $.age
に置き換えて、say $john.age;
の結果を見てみましょう。
9.3. 名前付き引数と固定パラメータ
Rakuでは、すべてのクラスはデフォルトの .new()
コンストラクタを継承しています。
このコンストラクタは引数を与えてオブジェクトを生成することもできます。
デフォルトのコンストラクタは 名前付き引数 のみ使用することができます。
上記の例において、 .new()
に与えられている引数がすべて名前付きであることに注目してください。
-
name => 'John'
-
age => 23
では、もしオブジェクトを生成するときにいちいち属性の名前を指定したくなかったらどうしたらいいでしょう?
そういう場合は、 固定引数 を受け取るような別のコンストラクタを作る必要があります。
class Human {
has $.name;
has $.age;
has $.sex;
has $.nationality;
# デフォルトのコンストラクタをオーバーライドする
method new ($name,$age,$sex,$nationality) {
self.bless(:$name,:$age,:$sex,:$nationality);
}
}
my $john = Human.new('John',23,'M','American');
say $john;
9.4. メソッド
9.4.1. イントロダクション
メソッドはオブジェクトの サブルーチン です。
サブルーチンのように、機能の集合をパッケージングするための手段であり、 引数 を受け取り、 シグネチャ を持ち、複数 あるとして定義することができます。
メソッドは method
キーワードを用いることで定義されます。
一般的な状況では、メソッドはオブジェクトの属性に対して何かしらの処理を行うことを要求されます。
これはカプセル化の考え方を強化します。オブジェクトの属性はメソッドを通じてオブジェクトの中からしか操作できません。
外の世界からはオブジェクトのメソッドとしかやりとりすることができず、そのオブジェクトの属性に直接アクセスすることはできません。
class Human {
has $.name;
has $.age;
has $.sex;
has $.nationality;
has $.eligible;
method assess-eligibility {
if self.age < 21 {
$!eligible = 'No'
} else {
$!eligible = 'Yes'
}
}
}
my $john = Human.new(name => 'John', age => 23, sex => 'M', nationality => 'American');
$john.assess-eligibility;
say $john.eligible;
一度クラスの中でメソッドが定義されたら、 ドット表記法 によってオブジェクトから呼び出すことができます。:
オブジェクト . メソッド として、上記の例のように: $john.assess-eligibility
メソッドの定義の中では、他のメソッドを呼び出すためにオブジェクトそれ自身への参照が必要な場合は self
キーワードを使います。
メソッドの定義の中では、属性を参照する必要がある場合は、その属性が .
をともなって定義されていても !
を使います。
そういったことを行う論理的根拠は、 .
トゥイジルが行っていることは !
をともなった属性を宣言し、アクセッサを自動で生成することであるということです。
上の例では if self.age < 21
と if $!age < 21
は同じ効果を持っているかもしれませんが、理屈の上では違うということになっています:
-
self.age
は.age
メソッド (アクセッサ) を呼びます
$.age
とも書けます -
$!age
は変数への直接の呼び出しです
9.4.2. プライベートメソッド
通常のメソッドはクラスの外でもオブジェクトから呼び出すことができます。
プライベートメソッド はクラスの中からしか呼び出せないメソッドです。
ユースケースとしては、あるメソッドが、特定の機能を使うために、ほかのメソッドを呼び出すようなときでしょう。
外の世界とインタフェースで接続されているメソッドはパブリックですが、そこから参照されているメソッドはプライベートなままであるべきです。
直接ユーザーに呼び出してほしくないのです。そのため、プライベートとして宣言することになります。
プライベートメソッドの宣言では名前の前で !
トゥイジルを使う必要があります。
プライベートメソッドは .
の代わりに !
によって呼び出します。
method !iamprivate {
# コードはここに
}
method iampublic {
self!iamprivate;
# さらに処理を行う
}
9.5. クラス属性
クラス属性 はクラス自身に属しているがオブジェクトには属していないような属性です。
定義のときに初期化することができます。
クラス属性は has
の代わりに my
をつかうことで宣言します。
クラス属性はそのオブジェクトではなくクラスそれ自身を呼び出します。
class Human {
has $.name;
my $.counter = 0;
method new($name) {
Human.counter++;
self.bless(:$name);
}
}
my $a = Human.new('a');
my $b = Human.new('b');
say Human.counter;
9.6. アクセスタイプ
今までの見てきた例では、アクセッサはオブジェクトの属性から情報を 得る ために使われてきました。
属性の値を変更する必要があるとしたらどうでしょう?
is rw
キーワードを使って、読み/書き ラベルを付与する必要があります。
class Human {
has $.name;
has $.age is rw;
}
my $john = Human.new(name => 'John', age => 21);
say $john.age;
$john.age = 23;
say $john.age;
デフォルトでは、すべての属性は 読み込み専用 として宣言されます。しかし明示的に is readonly
を使うこともできます。
9.7. 継承
9.7.1. イントロダクション
継承は オブジェクト指向プログラミングのもう一つの考え方です。
クラスを定義すると、たくさんのクラスで同じ属性/メソッドを使っているということにすぐ気づくでしょう。
コードは重複しているべきでしょうか?
ダメです! 継承 を使うべきです。
人間クラスと従業員クラスの二つのクラスを定義したいという状況を考えてみましょう。
人間は二つの属性を持っています: 名前と年齢
従業員は四つの属性を持っています: 名前、年齢、会社、給料
次のようにクラスを定義しようとするかもしれません:
class Human {
has $.name;
has $.age;
}
class Employee {
has $.name;
has $.age;
has $.company;
has $.salary;
}
理屈の上では正しいですが、上記のコードの考え方はお粗末です。
次のような書き方のほうが良いでしょう:
class Human {
has $.name;
has $.age;
}
class Employee is Human {
has $.company;
has $.salary;
}
is
キーワードは継承を宣言しています。
オブジェクト指向の用語では従業員は人間の 子 であり、人間は従業員の 親 であるといいます。
すべての子クラスは親クラスの属性とメソッドを継承します。そのため再定義する必要はありません。
9.7.2. オーバーライド
クラスは親クラスからすべての属性とメソッドを継承します。
継承したメソッドとは違うふるまいを子クラスのそれが行う必要がある場合があります。
こういった場合は子クラスにおいてメソッドを再定義します。
この考え方は オーバーライド と呼ばれます。
下記の例では、introduce-yourself
メソッドが従業員クラスによって継承されています。
class Human {
has $.name;
has $.age;
method introduce-yourself {
say 'Hi i am a human being, my name is ' ~ self.name;
}
}
class Employee is Human {
has $.company;
has $.salary;
}
my $john = Human.new(name =>'John', age => 23,);
my $jane = Employee.new(name =>'Jane', age => 25, company => 'Acme', salary => 4000);
$john.introduce-yourself;
$jane.introduce-yourself;
オーバーライドは次のように実行されます:
class Human {
has $.name;
has $.age;
method introduce-yourself {
say 'Hi i am a human being, my name is ' ~ self.name;
}
}
class Employee is Human {
has $.company;
has $.salary;
method introduce-yourself {
say 'Hi i am a employee, my name is ' ~ self.name ~ ' and I work at: ' ~ self.company;
}
}
my $john = Human.new(name =>'John',age => 23,);
my $jane = Employee.new(name =>'Jane',age => 25,company => 'Acme',salary => 4000);
$john.introduce-yourself;
$jane.introduce-yourself;
オブジェクトがどのクラスのものであるかに依存して、正しいメソッドが呼ばれます。
9.7.3. サブメソッド
サブメソッド は子クラスによって継承されないメソッドです。
宣言されたクラスからのみアクセスすることができます。
submethod
キーワードを使って定義されます。
9.8. 多重継承
Rakuでは多重継承を行うことができます。あるクラスは複数の他のクラスから継承を行うことができます。
class bar-chart {
has Int @.bar-values;
method plot {
say @.bar-values;
}
}
class line-chart {
has Int @.line-values;
method plot {
say @.line-values;
}
}
class combo-chart is bar-chart is line-chart {
}
my $actual-sales = bar-chart.new(bar-values => [10,9,11,8,7,10]);
my $forecast-sales = line-chart.new(line-values => [9,8,10,7,6,9]);
my $actual-vs-forecast = combo-chart.new(bar-values => [10,9,11,8,7,10],
line-values => [9,8,10,7,6,9]);
say "Actual sales:";
$actual-sales.plot;
say "Forecast sales:";
$forecast-sales.plot;
say "Actual vs Forecast:";
$actual-vs-forecast.plot;
出力
Actual sales:
[10 9 11 8 7 10]
Forecast sales:
[9 8 10 7 6 9]
Actual vs Forecast:
[10 9 11 8 7 10]
combo-chart
クラスは二つの系列を保持できるようになっているべきです。一つは実際の値でバーにプロットされます。
もう一つは予測値で線にプロットされます。
これが combo-chart
クラスを line-chart
クラスと bar-chart
クラスの子として定義した理由です。
combo-chart
の plot
メソッドが要求された結果を生成しなかったことに気づいたと思います。
系列一つだけがプロットされました。
なぜこんなことが起こったのでしょうか?
combo-chart
は line-chart
と bar-chart
を継承しており、両方とも plot
と呼ばれるメソッドを持っています。
combo-chart
からこのメソッドが呼ばれるとき、Rakuの内部では継承されたメソッドのうちの一つだけを呼ぶことでコンフリクトを解消しているのです。
正しくふるまうようにするには、 combo-chart
の中の plot
メソッドをオーバーライドするべきでした。
class bar-chart {
has Int @.bar-values;
method plot {
say @.bar-values;
}
}
class line-chart {
has Int @.line-values;
method plot {
say @.line-values;
}
}
class combo-chart is bar-chart is line-chart {
method plot {
say @.bar-values;
say @.line-values;
}
}
my $actual-sales = bar-chart.new(bar-values => [10,9,11,8,7,10]);
my $forecast-sales = line-chart.new(line-values => [9,8,10,7,6,9]);
my $actual-vs-forecast = combo-chart.new(bar-values => [10,9,11,8,7,10],
line-values => [9,8,10,7,6,9]);
say "Actual sales:";
$actual-sales.plot;
say "Forecast sales:";
$forecast-sales.plot;
say "Actual vs Forecast:";
$actual-vs-forecast.plot;
出力
Actual sales:
[10 9 11 8 7 10]
Forecast sales:
[9 8 10 7 6 9]
Actual vs Forecast:
[10 9 11 8 7 10]
[9 8 10 7 6 9]
9.9. ロール
ロール はクラスが属性とメソッドのコレクションであるという点においてクラスと似ています。
ロールは role
キーワードによって宣言されます。ロールを実装したいクラスは does
キーワードを使って宣言します。
role bar-chart {
has Int @.bar-values;
method plot {
say @.bar-values;
}
}
role line-chart {
has Int @.line-values;
method plot {
say @.line-values;
}
}
class combo-chart does bar-chart does line-chart {
method plot {
say @.bar-values;
say @.line-values;
}
}
my $actual-sales = bar-chart.new(bar-values => [10,9,11,8,7,10]);
my $forecast-sales = line-chart.new(line-values => [9,8,10,7,6,9]);
my $actual-vs-forecast = combo-chart.new(bar-values => [10,9,11,8,7,10],
line-values => [9,8,10,7,6,9]);
say "Actual sales:";
$actual-sales.plot;
say "Forecast sales:";
$forecast-sales.plot;
say "Actual vs Forecast:";
$actual-vs-forecast.plot;
上記のスクリプトを実行すると全く同じ結果が出力されることを確認できるはずです。
そろそろこんなひとり言が聞こえてきそうです: もしロールがクラスのようにふるまうなら、ロールの使い道って何だろう?
この質問に答えるために、多重継承の例を見せるために使われた最初のスクリプトを修正してください。
plot
メソッドをオーバーライドするのを 忘れた 例のスクリプトです。
role bar-chart {
has Int @.bar-values;
method plot {
say @.bar-values;
}
}
role line-chart {
has Int @.line-values;
method plot {
say @.line-values;
}
}
class combo-chart does bar-chart does line-chart {
}
my $actual-sales = bar-chart.new(bar-values => [10,9,11,8,7,10]);
my $forecast-sales = line-chart.new(line-values => [9,8,10,7,6,9]);
my $actual-vs-forecast = combo-chart.new(bar-values => [10,9,11,8,7,10],
line-values => [9,8,10,7,6,9]);
say "Actual sales:";
$actual-sales.plot;
say "Forecast sales:";
$forecast-sales.plot;
say "Actual vs Forecast:";
$actual-vs-forecast.plot;
出力
===SORRY!===
Method 'plot' must be resolved by class combo-chart because it exists in multiple roles (line-chart, bar-chart)
もし複数のロールが同じクラスに対して適用され、コンフリクトがあるなら、コンパイルタイムのエラーが投げられます。
これは、多重継承よりもずっと安全なアプローチです。なぜなら、多重継承ではコンフリクトはエラーとして考えられておらずランタイムで単純に解決されてしまうからです。
ロールはコンフリクトがあるときに警告してくれるのです。
9.10. イントロスペクション
イントロスペクション は、オブジェクトの型、オブジェクトの属性、オブジェクトのメソッドといったオブジェクトの情報を得るための処理です。
class Human {
has Str $.name;
has Int $.age;
method introduce-yourself {
say 'Hi i am a human being, my name is ' ~ self.name;
}
}
class Employee is Human {
has Str $.company;
has Int $.salary;
method introduce-yourself {
say 'Hi i am a employee, my name is ' ~ self.name ~ ' and I work at: ' ~ self.company;
}
}
my $john = Human.new(name =>'John',age => 23,);
my $jane = Employee.new(name =>'Jane',age => 25,company => 'Acme',salary => 4000);
say $john.WHAT;
say $jane.WHAT;
say $john.^attributes;
say $jane.^attributes;
say $john.^methods;
say $jane.^methods;
say $jane.^parents;
if $jane ~~ Human {say 'Jane is a Human'};
イントロスペクションは次のように容易に行えます:
-
.WHAT
— オブジェクトがどのクラスから作られたかを返します。 -
.^attributes
— オブジェクトのすべての属性を返します。 -
.^methods
— オブジェクトから呼ぶことのできるすべてのメソッドを返します。 -
.^parents
— オブジェクトの属しているクラスの親クラスを返します。 -
~~
はスマートマッチ演算子を呼びます。 もしオブジェクトが比較している相手のクラスか、その相手のクラスの継承先のいずれかのクラスから生成されているなら True と評価されます。
もしRakuにおけるオブジェクト指向についてより深く知りたいのであれば、次のURLを参照することをすすめます |
10. 例外処理
10.1. 例外のキャッチ
例外 はランライムで何かが失敗したときに発生する特別なふるまいです。
例外が 投げられる と表現します。
正しく実行される下記のスクリプトについて考えてみてください:
my Str $name;
$name = "Joanna";
say "Hello " ~ $name;
say "How are you doing today?"
出力
Hello Joanna
How are you doing today?
では、例外を投げる次のスクリプトについて考えてみてください:
my Str $name;
$name = 123;
say "Hello " ~ $name;
say "How are you doing today?"
出力
Type check failed in assignment to $name; expected Str but got Int
in block <unit> at exceptions.raku:2
エラーが発生したとき(この場合は文字列変数に数値を代入している)は必ずプログラムが停止し、ほかの行のコードは評価されないということに注目してください。
エラー処理 は 投げられた 例外の キャッチ 処理を行うことでスクリプトが実行を続けられるようにすることです。
my Str $name;
try {
$name = 123;
say "Hello " ~ $name;
CATCH {
default {
say "Can you tell us your name again, we couldn't find it in the register.";
}
}
}
say "How are you doing today?";
出力
Can you tell us your name again, we couldn't find it in the register.
How are you doing today?
例外処理は try-catch
ブロックを用いることで行われます。
try {
# コードはここに
# もし何かが失敗したなら下記のCATCHブロックに入ります
# もし問題がなかったのなら下記のCATCHブロックは無視されます
CATCH {
default {
# ここのコードは例外が投げられたときだけ評価されます
}
}
}
CATCH
ブロックは given
ブロックが定義されるときと同じように定義できます。
これは様々なタイプの例外を キャッチ して扱うことができることを意味しています。
try {
# コードはここに
# もし何かが失敗したなら下記のCATCHブロックに入ります
# もし問題がなかったのなら下記のCATCHブロックは無視されます
CATCH {
when X::AdHoc { # X::AdHoc型の例外が投げられたのなら何かを実行します }
when X::IO { # X::IO型の例外が投げられたのなら何かを実行します }
when X::OS { # X::OS型の例外が投げられたのなら何かを実行します }
default { # 上記の型に該当しない例外が投げられたのなら何かを実行します }
}
}
10.2. 例外を投げる
Rakuは明示的に例外を投げることができます。
二つのタイプの例外を投げることができます:
-
アドホック例外
-
型付き例外
my Int $age = 21;
die "Error !";
my Int $age = 21;
X::AdHoc.new(payload => 'Error !').throw;
アドホック例外は、例外メッセージのともなった die
サブルーチンを使って投げられます。
型付き例外はオブジェクトです。したがって上記の例では .new()
コンストラクタを使用しています。
すべての型付き例外はクラス X
の子孫です。下記は少数の例です:
X::AdHoc
は最もシンプルな例外のタイプです
X::IO
はIOエラーに関する例外です
X::OS
はOSエラーに関する例外です
X::Str::Numeric
は文字列を数値にしようとすることに関する例外です
もし例外の型と、関連するメソッドのすべてのリストを知りたいのであれば、次のURLを参照することをすすめます https://docs.raku.org/type.html |
11. 正規表現
正規表現、または regex はパターンマッチングのための文字のシーケンスです。 パターンだと思ってください。
if 'enlightenment' ~~ m/ light / {
say "enlightenment contains the word light";
}
この例では、スマートマッチ演算子 ~~
は文字列(enlightenment)が単語(light)を含んでいるかどうか調べるのに使われています。
"Enlightenment" は 正規表現 m/ light /
にマッチします。
11.1. 正規表現の定義
正規表現は次のように定義できます:
-
/light/
-
m/light/
-
rx/light/
明示的に指定されない限り、空白は無視されます。つまり、m/light/
と m/ light /
は等価です。
11.2. 文字のマッチング
アルファベット文字とアンダースコア _
はそのまま書かれます。
他の文字はバックスラッシュを使うかクォートで囲むことでエスケープされている必要があります。
if 'Temperature: 13' ~~ m/ \: / {
say "The string provided contains a colon :";
}
if 'Age = 13' ~~ m/ '=' / {
say "The string provided contains an equal character = ";
}
if 'name@company.com' ~~ m/ "@" / {
say "This is a valid email address because it contains an @ character";
}
11.3. 文字種へのマッチング
文字は文字種に分類することができ、これらに対してマッチングを行うことができます。
またその分類と逆の分類(その分類を除いたものすべて)に対してマッチングを行うこともできます。
種類 |
正規表現 |
逆 |
正規表現 |
単語構成文字 (文字、数字、アンダースコア) |
\w |
非単語構成文字 |
\W |
数字 |
\d |
非数字 |
\D |
空白文字 |
\s |
非空白文字 |
\S |
水平空白文字 |
\h |
非水平空白文字 |
\H |
垂直空白文字 |
\v |
非垂直空白文字 |
\V |
タブ |
\t |
非タブ |
\T |
改行 |
\n |
非改行 |
\N |
if "John123" ~~ / \d / {
say "This is not a valid name, numbers are not allowed";
} else {
say "This is a valid name"
}
if "John-Doe" ~~ / \s / {
say "This string contains whitespace";
} else {
say "This string doesn't contain whitespace"
}
11.4. ユニコードのプロパティ
前節での文字種に対するマッチングは便利です。
そうはいっても、もっと系統的なアプローチはユニコードのプロパティを使うことです。
標準ASCII文字コードの範囲内の文字種に対しても、そうでない文字種に対してもマッチングができるようになります。
ユニコードのプロパティは <: >
で囲まれます。
if "Devangari Numbers १२३" ~~ / <:N> / {
say "Contains a number";
} else {
say "Doesn't contain a number"
}
if "Привет, Иван." ~~ / <:Lu> / {
say "Contains an uppercase letter";
} else {
say "Doesn't contain an upper case letter"
}
if "John-Doe" ~~ / <:Pd> / {
say "Contains a dash";
} else {
say "Doesn't contain a dash"
}
11.5. ワイルドカード
正規表現ではワイルドカードも用いることができます。
ドット .
は任意の一文字を意味します。
if 'abc' ~~ m/ a.c / {
say "Match";
}
if 'a2c' ~~ m/ a.c / {
say "Match";
}
if 'ac' ~~ m/ a.c / {
say "Match";
} else {
say "No Match";
}
11.6. 量指定子
量指定子は文字の後に付けられ、その文字が何回出現するのか指定するために使われます。
クエスチョンマーク ?
は0か1回を意味します。
if 'ac' ~~ m/ a?c / {
say "Match";
} else {
say "No Match";
}
if 'c' ~~ m/ a?c / {
say "Match";
} else {
say "No Match";
}
スター *
は0か複数回を意味します。
if 'az' ~~ m/ a*z / {
say "Match";
} else {
say "No Match";
}
if 'aaz' ~~ m/ a*z / {
say "Match";
} else {
say "No Match";
}
if 'aaaaaaaaaaz' ~~ m/ a*z / {
say "Match";
} else {
say "No Match";
}
if 'z' ~~ m/ a*z / {
say "Match";
} else {
say "No Match";
}
+
は少なくとも一回を意味します。
if 'az' ~~ m/ a+z / {
say "Match";
} else {
say "No Match";
}
if 'aaz' ~~ m/ a+z / {
say "Match";
} else {
say "No Match";
}
if 'aaaaaaaaaaz' ~~ m/ a+z / {
say "Match";
} else {
say "No Match";
}
if 'z' ~~ m/ a+z / {
say "Match";
} else {
say "No Match";
}
11.7. マッチ結果
正規表現に対する文字列のマッチング処理が成功したときはいつでも、
そのマッチ結果は特別な変数 $/
に格納されます。
if 'Rakudo is a Perl 6 compiler' ~~ m/:s Perl 6/ {
say "The match is: " ~ $/;
say "The string before the match is: " ~ $/.prematch;
say "The string after the match is: " ~ $/.postmatch;
say "The matching string starts at position: " ~ $/.from;
say "The matching string ends at position: " ~ $/.to;
}
The match is: Perl 6
The string before the match is: Rakudo is a
The string after the match is: compiler
The matching string starts at position: 12
The matching string ends at position: 18
$/
は マッチオブジェクト (正規表現がマッチした文字列) を返します
マッチオブジェクト から次のようなメソッドを呼ぶことができます:
.prematch
はマッチの前の文字列を返します。
.postmatch
はマッチの後ろの文字列を返します。
.from
はマッチの開始位置を返します。
.to
はマッチの終了位置を返します。
デフォルトでは正規表現の定義における空白文字は無視されます。 もし、空白文字を含んだ正規表現に対してマッチさせたいのであれば明示的にそうする必要があります。 正規表現 m/:s Perl 6/ の中の :s は空白文字も考慮するように強制します。別の選択肢としては m/ Perl\s6 / と書くこともできます。\s は空白文字を表します。もし正規表現が空白文字を一つより多く含んでいるなら、 :s を使うと \s を空白文字が出現する箇所でいちいち書くのと比べると良い選択肢です。
|
11.8. 例
emailが正しいかどうか調べましょう。
この例のために正しいemailのアドレスは次のような形式であるとしましょう:
ファーストネーム [dot] ラストネーム [at] 会社名 [dot] (com/org/net)
この例で使われている正規表現はあまり正確ではありません。 Rakuにおける正規表現の機能を説明することが唯一の目的です。 プロダクションでそのまま使わないでください。 |
my $email = 'john.doe@perl6.org';
my $regex = / <:L>+\.<:L>+\@<:L+:N>+\.<:L>+ /;
if $email ~~ $regex {
say $/ ~ " is a valid email";
} else {
say "This is not a valid email";
}
john.doe@perl6.org is a valid email
<:L>
は一つの文字にマッチします
<:L>` は一つ以上の文字にマッチします +
`\.` は一つの[dot] 文字にマッチします +
`\@` は一つの[at] 文字にマッチします +
`<:L:N>
は一つの文字か数字にマッチします
<:L+:N>+
は一つの文字か数字の繰り返しにマッチします
この正規表現は次のように分解することができます。:
-
ファーストネーム
<:L>+
-
[dot]
\.
-
ラストネーム
<:L>+
-
[at]
\@
-
会社名
<:L+:N>+
-
[dot]
\.
-
com/org/net
<:L>+
my $email = 'john.doe@perl6.org';
my regex many-letters { <:L>+ };
my regex dot { \. };
my regex at { \@ };
my regex many-letters-numbers { <:L+:N>+ };
if $email ~~ / <many-letters> <dot> <many-letters> <at> <many-letters-numbers> <dot> <many-letters> / {
say $/ ~ " is a valid email";
} else {
say "This is not a valid email";
}
名前付き正規表現は次のような文法で定義されます: my regex regex-name { regex definition }
名前付き正規表現は次のような文法で呼び出されます: <regex-name>
もしもっと正規表現について知りたいのであれば、次のURLを参照するのをすすめます https://docs.raku.org/language/regexes |
12. Rakuのモジュール
Rakuは汎用プログラミング言語です。下記を含む多数のタスクに取り組むのに使うことができます。 テキスト操作、グラフィックス、ウェブ、データベース、ネットワークプロトコルなど。
再利用性はとても重要な概念です、それによってプログラマは新しいタスクに取り組もうとするたびに車輪の再発明を行う必要がなくなります。
Rakuはでは モジュール の作成と再配布ができます。それぞれのモジュールはインストールされれば再利用できる機能のパッケージです。
Zef はRakudo Starに付属しているモジュール管理ツールです。
特定のモジュールをインストールするには、次のコマンドをターミナルで打ってください:
zef install "モジュールの名前"
Rakuのモジュールの一覧を見るには次のURLを参照してください: https://modules.perl6.org/ |
12.1. モジュールの使用
MD5は128ビットのハッシュ値を生成する暗号学的ハッシュ関数です。
MD5は、データベースに格納されているパスワードの暗号化など様々なアプリケーションで使われています。
新たなユーザーが登録されるとき、資格情報は平文として保存されずに ハッシュ 化されます。
この背景にある根拠は、もしDBがハッキングの被害にあっていたとしても、攻撃者はパスワードが何であるかを知ることができないということです。
幸運なことに、MD5アルゴリズムを実装する必要はありません。MD5アルゴリズムを実装したRakuモジュールがすでにあります。
インストールしましょう:
zef install Digest::MD5
では、次のスクリプトを実行してください:
use Digest::MD5;
my $password = "password123";
my $hashed-password = Digest::MD5.new.md5_hex($password);
say $hashed-password;
ハッシュを生成する md5_hex()
関数を実行するために、この関数の実行に必要なモジュールをロードしなくてはなりません。
use
キーワードはスクリプトの中で使いたいモジュールをロードします。
実用的にはMD5ハッシュ単独では不十分です、なぜなら辞書攻撃を受けやすいからです。 サルトと組み合わせるべきです。 https://en.wikipedia.org/wiki/Salt_(cryptography). |
13. ユニコード
ユニコードは標準的なエンコーディングで、世界のほとんどの書込システムにおいてテキストを表現します。
UTF-8は、ユニコードにおける、すべての文字や符号位置をエンコーディングすることができる文字エンコードです。
文字は次によって定義されます:
書記素: 視覚的表現。
符号位置: 文字に割り当てられた数。
符号位置の名前: 文字に割り当てられた名前。
13.1. ユニコードの使用
say "a";
say "\x0061";
say "\c[LATIN SMALL LETTER A]";
上記の三つの行は文字を作るためにそれぞれ異なった方法をとっています:
-
直接文字を書く (書記素)
-
\x
と符号位置を使う -
\c
と符号位置の名前を使う
say "☺";
say "\x263a";
say "\c[WHITE SMILING FACE]";
say "á";
say "\x00e1";
say "\x0061\x0301";
say "\c[LATIN SMALL LETTER A WITH ACUTE]";
a
は次のように書けます:
-
ユニークな符号位置
\x00e1
を使う -
もしくは
a
とアキュート・アクセントの符号位置を組み合わせる\x0061\x0301
say "á".NFC;
say "á".NFD;
say "á".uniname;
出力
NFC:0x<00e1>
NFD:0x<0061 0301>
LATIN SMALL LETTER A WITH ACUTE
NFC
はユニークな符号位置を返します。
NFD
は文字を分解し、それぞれの符号位置を返します。
uniname
は符号位置の名前を返します。
my $Δ = 1;
$Δ++;
say $Δ;
my $var = 2 + ⅒;
say $var;
13.2. ユニコードを考慮した操作
13.2.1. 数値
アラビア数字は十個あります: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 この数字は世界でもっとも使われているものです。
とはいえ、世界には異なる数字を使うような少数派の地域があります。
アラビア数字以外の数字を使うときに特別な注意は必要ありません。 すべてのメソッド/演算子は期待通りに動きます。
say (٤,٥,٦,1,2,3).sort; # (1 2 3 4 5 6)
say 1 + ٩; # 10
13.2.2. 文字列
一般的な文字列操作をしようとしたとき、特に比較やソートのときに、常に求めていた結果を得ることができるわけではないかもしれません。
比較
say 'a' cmp 'B'; # More
上記の例では a
は B
よりも大きいということを示しています。
小文字の a
の符号位置は大文字の B
の符号位置よりも大きいというのがその理由です。
技術的には正しい一方、ひょっとしたら求めていたものとは違うかもしれません。
幸運なことにRakuは ユニコード照合アルゴリズム を実装したメソッド/演算子を持っています。
その一つが unicmp
です。これは上記で示した cmp
のようにふるまいますがこのアルゴリズムを使用する点が異なります。
say 'a' unicmp 'B'; # Less
見てわかる通り、 unicmp
演算子を使うことで a
は B
より小さいという期待通りの結果が得られました。
ソート
符号位置を利用したソートにおいて sort
メソッドの他の選択肢として、Rakuは ユニコード照合アルゴリズム を実装した collate
メソッドを提供しています。
say ('a','b','c','D','E','F').sort; # (D E F a b c)
say ('a','b','c','D','E','F').collate; # (a b c D E F)
14. 並列処理、並行性、非同期性
14.1. 並列処理
一般的な状況では、プログラムのすべてのタスクは上から順に実行されます。
もし、たくさんの時間がかかるようなことを行おうとしているのでないかぎりは問題にはなりません。
ありがたいことに、Rakuには並列実行のための機能があります。
現在のところ、次の二つの事柄のうちの一つを意味するということを頭にとどめておくことが重要です:
-
タスクの並列処理: 二つ(もしくはそれ以上)の独立した式が並列実行されます。
-
データの並列処理: 一つの式が要素のリストに対して並列的に反復処理を行います。
まずは後者の方から始めましょう.
14.1.1. データの並列処理
my @array = 0..50000; # 配列の作成
my @result = @array.map({ is-prime $_ }); # それぞれの配列の要素に対して is-prime を呼ぶ
say now - INIT now; # スクリプトの処理が完了するまでにかかる時間を出力
@array.map({ is-prime $_ })
という操作を行っているだけです
配列のそれぞれの要素に対して is-prime
サブルーチンが経時的に呼び出されています:
is-prime @array[0]
、is-prime @array[1]
、is-prime @array[2]
・・・の順です
is-prime
を複数の配列の要素に対して同時に呼び出すことができます:my @array = 0..50000; # 配列の作成
my @result = @array.race.map({ is-prime $_ }); # それぞれの配列の要素に対して is-prime を呼ぶ
say now - INIT now; # スクリプトの処理が完了するまでにかかる時間を出力
式の中で race
を使用していることに注目してください。
このメソッドは配列に対する並列的な反復処理を可能にします。
両方の例( race
の有る方と無い方)を実行したのち、両方のスクリプトにおいて処理が完了するのにかかる時間を比べてください。
race
hyper
もし両方の例を実行したなら、片方はソートされていてもう片方はソートされていないことに気づいたはずです。 |
14.1.2. タスクの並列処理
my @array1 = 0..49999;
my @array2 = 2..50001;
my @result1 = @array1.map( {is-prime($_ + 1)} );
my @result2 = @array2.map( {is-prime($_ - 1)} );
say @result1 eqv @result2;
say now - INIT now;
-
二つの配列を定義しました
-
それぞれの配列に対して異なる操作を適用し、結果を保存しました
-
そして、両方の結果が同じであるかを調べました
このスクリプトは @array1.map( {is-prime($_ + 1)} )
が終了するのを待っています
それから、@array2.map( {is-prime($_ - 1)} )
を評価します。
それぞれの配列に対して適用された操作の両方が互いに依存していません。
my @array1 = 0..49999;
my @array2 = 2..50001;
my $promise1 = start @array1.map( {is-prime($_ + 1)} ).eager;
my $promise2 = start @array2.map( {is-prime($_ - 1)} ).eager;
my @result1 = await $promise1;
my @result2 = await $promise2;
say @result1 eqv @result2;
say now - INIT now;
start
サブルーチンはコードを評価し、Promise型のオブジェクト
(端的には 約束
)を返します。
もしコードが正しく評価されたのなら 約束 は 守られ ているでしょう。
もしコードが例外を投げたのなら 約束 は 破られ ているでしょう。
await
サブルーチンは 約束 を待ちます。
もし約束が 守られた なら返された値を取得するでしょう。
もし約束が 破られた なら投げられた例外を取得するでしょう。
それぞれのスクリプトにおいて処理が終了するのにかかった時間を調べてください。
並列処理にはスレッディングのオーバーヘッドがあります。もしオーバーヘッドが計算速度で相殺されないのなら、スクリプトが遅くなってしまったように見えるでしょう。 これが race 、 hyper 、 start 、 await をいたってシンプルなスクリプトに対して用いると実際には遅くなってしまう理由です。
|
14.2. 並行性と非同期性
並行/非同期プログラミングについてもっと情報を知りたいのなら、次のURLを参照してください: https://docs.raku.org/language/concurrency |
15. ネイティブコールインタフェース
Rakuではネイティブコール (Native Call) インタフェースを用いて、Cのライブラリを使うことができます。
NativeCall
はRakuと一緒に提供されている標準モジュールです
15.1. 関数の呼び出し
hellofromc
という関数が定義されている下記コードについて考えてみましょう。
この関数はターミナルに Hello from C
と表示します。
この関数は引数を受け付けませんし返り値もありません。
#include <stdio.h>
void hellofromc () {
printf("Hello from C\n");
}
OSに応じて、次のコマンドを実行し、上記のCのコードをライブラリへとコンパイルしてください。
gcc -c -fpic ncitest.c
gcc -shared -o libncitest.so ncitest.o
gcc -c ncitest.c
gcc -shared -o ncitest.dll ncitest.o
gcc -dynamiclib -o libncitest.dylib ncitest.c
Cのライブラリをコンパイルしたのと同じディレクトリで、次のコードを含むRakuのファイルを作り、実行してください。
use NativeCall;
constant LIBPATH = "$*CWD/ncitest";
sub hellofromc() is native(LIBPATH) { * }
hellofromc();
まずはじめに、 NativeCall
モジュールを使うということを宣言しました。
次に、Cのライブラリへのパスを保持している定数 LIBPATH
を作りました。
$*CWD
はカレントディレクトリを返すということに注意してください。
それから、 hellofromc()
という新しいRakuのサブルーチンを作りました。
このサブルーチンは、LIBPATH
下のCのライブラリの同じ名前を持った対応する関数のラッパーとしてふるまうでしょう。
is native
トレイトを用いることでこれを実現できます。
さいごにRakuのサブルーチンを呼び出しました。
突き詰めると、すべては is native
を使用して、Cのライブラリと同じ名前を持つサブルーチンを宣言することに帰着します。
15.2. 関数の名前の変更
前節では、is native
トレイトを使って同じ名前を持ったRakuのサブルーチンでラップすることで、どのようにしてCの関数を呼び出せるかを見てきました。
場合によっては、Rakuのサブルーチンの名前を変えたくなるかもしれません。
そうしたいときは、 is symbol
トレイトを使います。
上記のRakuのスクリプトを変更し、 Rakuのサブルーチンの名前を hellofromc
の代わりに hello
と付けてみましょう。
use NativeCall;
constant LIBPATH = "$*CWD/ncitest";
sub hello() is native(LIBPATH) is symbol('hellofromc') { * }
hello();
この場合RakuのサブルーチンはCの対応する関数と異なる名前を持っています。
元のCの関数の名前を用いて is symbol
トレイトを使うべきです。
15.3. 引数渡し
次の改変されたCのライブラリをコンパイルし、その下のRakuのスクリプトを実行しましょう。
どのようにしてCとRakuのコードの両方を、文字列を受け取るために改変したかについて注目してください。 (Cでは char*
で、Rakuでは Str
です)
#include <stdio.h>
void hellofromc (char* name) {
printf("Hello, %s! This is C!\n", name);
}
use NativeCall;
constant LIBPATH = "$*CWD/ncitest";
sub hello(Str) is native(LIBPATH) is symbol('hellofromc') { * }
hello('Jane');
15.4. 値の返却
さっきと同じことをもう一度行い、ふたつの整数を受け取って足し合わせる単純な計算機を生成しましょう。
CのライブラリをコンパイルしてRakuのスクリプトを実行してください。
int add (int a, int b) {
return (a + b);
}
use NativeCall;
constant LIBPATH = "$*CWD/ncitest";
sub add(int32,int32 --> int32) is native(LIBPATH) { * }
say add(2,3);
どのようにしてCとRakuの関数の両方がふたつの整数を受け取りひとつの整数を返しているかについて注目してください。 ( Cでは int
でRakuでは int32
です)
15.5. 型
もしかしたら先ほどのRakuのスクリプトで Int
ではなくて int32
を使ったのはなぜなのか疑問に思ったかもしれません。
Int
, Rat
などといったいくつかのRakuの型はCの関数からの値を通して受け取るときにそのままでは使うことができません。
Rakuで使う型はCの型と同じでなければなりません。
幸運なことに、RakuはCでその型に相当する型と対応付けられた型を提供しています。
Cの型 | Rakuの型 |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
配列: 例えば |
|
ネイティブコール インタフェースについてより詳しく知りたい方は、次のURLを参照してください https://docs.raku.org/language/nativecall |
16. コミュニティ
-
#raku IRCチャンネルです。活発な議論が行われています。何でも気軽に質問してください。簡単でよいからすぐに回答がほしい場合に適しています。: https://raku.org/community/irc
-
StackOverflow Raku questions はIRCよりももっと詳しい回答がほしい場合に適しています。
-
rakudoweekly Rakudoとその周辺の出来事について今週のダイジェストをお伝えします。
-
pl6anet Rakuのブログを集約しています。Rakuにフォーカスしたブログ記事にこうご期待ください。
-
/r/rakulang Rakuのsubredditを購読しましょう。
-
@raku_news twitterでコミュニティをフォローしましょう。