Perl Hackers Hub

第39回Perl 6の歩き方―15年越しでリリースされた新バージョン(2)

前回の(1)こちらから。

関数─⁠─Perl 6の特徴的文法

Perl 6には本当にたくさんの機能があります。ページは限られているので何を紹介すべきか迷いましたが、本稿では関数を取り上げます。

Perl 6の関数にはいくつかの種類があります。そして、それらにいろいろな機能が備わっています。

関数の種類

関数には、Sub、Block、WhateverCodeがあります。そしてそれらをまとめるCallableロールがあります。

Sub─⁠─普通の関数

普通の関数Subは、subキーワードで定義します。

sub hello($name) {
  "hello, $name!".say;
}

Block─⁠─実行されうるもの

Blockである{}を用いても、⁠実行されうるもの」を定義できます。

# トピック変数$_で引数を使える
my $is-even = { $_ %% 2 };

# -> 引数1, 引数2, ... の形で引数を宣言可能
my $distance1 = -> $x, $y {
  ($x ** 2 + $y ** 2).sqrt
};

# $^記号で明示的に宣言せずに引数を利用可能
my $distance2 = {
  ($^x ** 2 + $^y ** 2).sqrt
};

# for、if、whileなどもこのブロックが使われている
for 0..9 -> $x, $y {
  say $x ~ $y;
}

関数Subと、このBlockの違いはreturnの扱いにあります。関数内でreturnするともちろんその関数から復帰しますが、Block内でreturnすると外側の関数から復帰しようとします。すなわち、

sub even-array() {
  (^10).map: { return 2 * $_ };
}

return 2 * $_mapに渡したBlockから復帰するのではなく、外側の関数even-array()から復帰しようとします。この例では、おそらくそれは意図と違うでしょうから、

sub even-array() {
  (^10).map: { 2 * $_ };
}

と書く必要があります。

WhateverCode─⁠─さらにカジュアルな実行されうるもの

WhateverCodeである*によって、さらにカジュアルに「実行されうるもの」を定義できます。

my $half = * / 2; # -> $x { $x / 2 } と等価
$half(10); # 5

# 「偶数を引数にとる」を簡潔に表現できる
sub foo(Int $even where * %% 2) { ... }

Callable─⁠─関数をまとめるロール

ここまで紹介したSub、Block、WhateverCodeは、Callableロールのもとにまとまります。ロールとはほかの言語でのインタフェースに相当するもので、振る舞いを定義します。そしてSub、Block、WhateverCodeは、⁠実行されうる」という振る舞いを表すロールであるCallableを実装しているのです。

さて、実のところ任意のクラスをCallableロールを実装して定義すれば、それは「実行されうるもの」として扱えます。

class Foo does Callable {
  method CALL-ME($arg) { ... }
}

my &foo = Foo.new;
&foo("arg"); # CALL-MEが呼ばれる

クラスを用いれば、メンバー変数で状態を持った関数を作る、再利用可能な関数を作るなど、いろいろな可能性が出てきます。

引数、戻り値の宣言

すでにいくつかのコードに出てきましたが、Perl 6では関数を定義するときに引数を宣言できます。また、引数の型、戻り値の型も宣言できます。

# 引数を宣言
sub foo($n, $s) { ... }

# 引数を型とともに宣言
sub foo(Int $n, Str $s) { ... }

# 戻り値の型も宣言
sub foo(Int $n, Str $s --> Callable) { ... }

この(Int $n, Str $s --> Callable)のような関数の引数、戻り値の情報を、シグネチャと呼びます。

多重ディスパッチ

multiキーワードを使えば、同じ名前でシグネチャが異なる関数を複数定義し、それらを呼び分けられます。

multi foo(Str $s) { ... }
multi foo(Int $n) { ... }
multi foo($other) { ... }

foo(10); # foo(Int $n)が呼ばれる
foo({ say "hello" }); # foo($other)が呼ばれる

この多重ディスパッチが特に威力を発揮するのはMAIN関数の場合です。

まず、MAIN関数について説明します。MAIN関数はプログラムが実行されると自動的に呼び出される関数で、コマンドライン引数からオプションをパースするという便利な機能が付いています。たとえば次のように定義すると、

script.p6
sub MAIN(Bool :$verbose, Str :$prefix = "/opt") {
  say "-> verbose mode" if $verbose;
  say "-> prefix is $prefix";
}

以下に示すようにオプションを簡単に扱えます。

オプションがパースされる
$ perl6 script.p6 --verbose --prefix /usr/local
-> verbose mode
-> prefix is /usr/local
自動的にヘルプメッセージも生成される
$ perl6 script.p6 --help
Usage:
  script.p6 [--verbose] [--prefix=<Str>]

このMAIN関数は、多重ディスパッチを用いることでさらに便利になります。たとえば先ほど使ったモジュール管理ツールpandaには、次のようなMAIN関数が定義されています。

# pandaから一部改変して抜粋
multi MAIN('install', *@module, Str :$prefix) {
...
}
multi MAIN('list', Bool :$installed) {
...
}

こうすることで、いわゆるサブコマンドによってMAIN関数を呼び分けられます。すなわちpanda install...ならmulti MAIN('install', ...)が呼ばれ、panda list ...ならmulti MAIN('list', ...)が呼ばれます。自分でサブコマンドによる分岐を書くより、プログラムがすっきりします。

関数のラップ

関数をラップして、その関数の前処理、後処理を付け加えることができます。

sub distance($x, $y) {
  ($x ** 2 + $y ** 2).sqrt;
}

say distance(3, 4); # 5

# distanceをラップ
my $squashed = &distance.wrap: -> $x, $y {
  # callwithによってオリジナルの関数(distance)を
  # 呼び出す
  callwith($x * 2, $y / 2);
};

say distance(3, 4); # 6.32...

# アンラップしてもとに戻す
&distance.unwrap($squashed);

say distance(3, 4); # 5

もしあるブロック内だけ関数をラップしたいのであれば、ブロックを抜けたときの処理を指定できるLEAVEとともに使うとよいでしょう。

{
  my $squashed = &distance.wrap: -> $x, $y {
    callwith($x * 2, $y / 2);
  };
  LEAVE &distance.unwrap($squashed);
  say distance(3, 4); # 6.32...
}
# ブロックを抜けると自動的にもとに戻る
say distance(3, 4); # 5

Perl 6の関数には魅力的な機能がまだまだたくさんあります。詳しくはSynopsis 6: Subroutinesを参照してください。

<続きの(3)こちら。>

WEB+DB PRESS

本誌最新号をチェック!
WEB+DB PRESS Vol.130

2022年8月24日発売
B5判/168ページ
定価1,628円
(本体1,480円+税10%)
ISBN978-4-297-13000-8

  • 特集1
    イミュータブルデータモデルで始める
    実践データモデリング

    業務の複雑さをシンプルに表現!
  • 特集2
    いまはじめるFlutter
    iOS/Android両対応アプリを開発してみよう
  • 特集3
    作って学ぶWeb3
    ブロックチェーン、スマートコントラクト、NFT

おすすめ記事

記事・ニュース一覧