スキップしてメイン コンテンツに移動

Perl 5 to 6 - サブルーチンとシグネチャ

これはMoritz Lenz氏のWebサイトPerlgeek.deで公開されているブログ記事"Perl 5 to 6" Lesson 04 - Subroutines and Signaturesの日本語訳です。

原文はCreative Commons Attribution 3.0 Germanyに基づいて公開されています。

本エントリにはCreative Commons Attribution 3.0 Unportedを適用します。

Original text: Copyright© 2008-2010 Moritz Lenz

Japanese translation: Copyright© 2011 SATOH Koichi

NAME

"Perl 5 to 6" Lesson 04 - サブルーチンとシグネチャ

SYNOPSIS

# シグネチャなしのサブルーチン——Perl5風
sub print_arguments {
    say "Arguments:";
    for @_ {
        say "\t$_";
    }
}

# 固定引数の型指定付きシグネチャ
sub distance(Int $x1, Int $y1, Int $x2, Int $y2) {
    return sqrt ($x2-$x1)**2 + ($y2-$y1)**2;
}
say distance(3, 5, 0, 1); 

# デフォルト引数
sub logarithm($num, $base = 2.7183) {
    return log($num) / log($base)
}
say logarithm(4);       # 第2引数はデフォルトを利用
say logarithm(4, 2);    # 明示的な第2引数

# 名前付き引数
sub doit(:$when, :$what) {
    say "doing $what at $when";
}
doit(what => 'stuff', when => 'once');  # 'doing stuff at once'
doit(:when<noon>, :what('more stuff')); # 'doing more stuff at noon'
# 不正: doit("stuff", "now")

DESCRIPTION

サブルーチンはsubキーワードで宣言され、CやJavaその他の言語と同様に形式的パラメータを持ちます。これらのパラメータは型制約をとることもできます。

パラメータはデフォルトでは読み出し専用です。この挙動はいわゆる「Trait」で変更できます:

sub foo($bar) {
    $bar = 2;       # 禁止されている
}

my $x = 2;
sub baz($bar is rw) {
    $bar = 0;       # 許される
}
baz($x); say $x;    # 0

sub quox($bar is copy){
    $bar = 3;
}
quox($x); say $x    # まだ0

パラメータは?を後ろに付けたり、デフォルト値を与えることでオプションにできます。

sub foo($x, $y?) {
    if $y.defined {
        say "WE CAN HAZ $y"
    }
}

sub bar($x, $y = 2 * $x) { 
    ...
}

名前付きパラメータ

my_sub($first, $second)のようにしてサブルーチンを呼び出すとき、$firstは最初の形式的パラメータに、$secondは2番目の形式的パラメータに、といった具合に結びつきます。このようなパラメータを「位置的」パラメータと呼んでいます。

数を数えるより名前を思い出す方が簡単なことは往々にしてあるので、Perl6は名前付きパラメータも持っています:

my $r = Rectangle.new( 
        x       => 100, 
        y       => 200, 
        height => 23,
        width  => 42,
        color  => 'black'
);

引数が具体的に何を意味しているか、見るなりすぐに理解できます。

名前付きパラメータを定義するには、単にパラメータの前に:を置くだけです:

sub area(:$width, :$height) {
    return $width * $height;
}
area(width => 2,  height => 3);
area(height => 3, width => 2 ); # 同じ
area(:height(3), :width(2));    # 同じ

ここまでの例では変数名がパラメータの名前としても使われていますが、別の名前を使うこともできます:

sub area(:width($w), :height($h)){
    return $w * $h;
}
area(width => 2,  height => 3);

名前付き引数は名前を使ってのみ渡すことができ、位置による指定はできません。一方で位置的引数は名前を使って渡すこともできます:

sub sqrt($number) { ... };
sqrt(3);
sqrt(number => 3); # これも動く

丸呑み(Slurpy)引数

サブルーチンにシグネチャを付けることは、前もって引数の数を知っておかなければならないということではありません。 残された引数を使い切る、丸呑み(slurpy)パラメータを(通常のパラメータの後に)定義することができます:

sub tail ($first, *@rest){
    say "First: $first";
    say "Rest: @rest[]";
}
tail(1, 2, 3, 4);           # "First: 1\nRest: 2 3 4\n"

変数展開

デフォルトでは配列は引数リストに展開されません。したがってPerl5とは異なり次のようなコードが書けます:

sub a($scalar1, @list, $scalar2){
    say $scalar2;
}

my @list = "foo", "bar";
a(1, @list, 2);                  # 2

これはデフォルトではリストを引数リストとして使えないということでもあります:

my @indexes = 1, 4;
say "abc".substr(@indexes)       # エラー!

プレフィクス|を付けると期待した動作をします:

say "abcdefgh".substr(|@indexes) # bcde

多重サブルーチン

異なるパラメータリストを持つ同名のサブルーチンを複数定義できます:

multi sub my_substr($str) { ... }                          # 1
multi sub my_substr($str, $start) { ... }                  # 2
multi sub my_substr($str, $start, $end) { ... }            # 3
multi sub my_substr($str, $start, $end, $subst) { ... }    # 4

このようなサブルーチンを呼び出すと、パラメータリストが一致するものが選ばれます。

多重サブルーチンは引数の数で区別できる必要はなく、パラメータの型でも区別します:

multi sub frob(Str $s) { say "Frobbing String $s"  }
multi sub frob(Int $i) { say "Frobbing Integer $i" }

frob("x")       # Frobbing String x
frob(2)         # Frobbing Integer 2

MOTIVATION

明示的な型シグネチャの有用性を疑う人はいません: タイピング量は削減され、重複引数の確認は省け、より自己文書化されたコードになります。 名前付きパラメータの価値も既に議論され切っています。

これはまた有用なイントロスペクションを提供します。例えばブロックかサブルーチンをArray.sortに渡したとして、そのコードが1つだけ引数を取るとすると、シュワルツ変換が自動的に行われます——このような機能はPerl5では不可能でした。明示的なシグネチャを欠いているため、sortはコードブロックが何個の引数を取るのか判断できないからです。

多重サブルーチンは組み込み関数を新しい型に対してオーバーライドできるという点で非常に便利です。例えばあなたが(大文字と小文字の変換に独特の規則がある)トルコ語の文字列を正しく扱えるようにローカライズされたバージョンのPerl6を欲しがっているとしましょう。

言語を改変する代わりに単にTurkishStrという新しい型を導入し、組み込み関数に多重サブルーチンを追加するだけです:

multi uc(TurkishStr $s) { ... }

これで文字列が言語に対応した型を持っているかだけ気をつければ、ucを通常の組み込み関数のように使えます。

演算子もサブルーチンなので、これらの改善点は演算子についても同様に働きます。

SEE ALSO

http://perlcabal.org/syn/S06.html

コメント

このブログの人気の投稿

京大テキストコーパスのパーサを書いた

要旨CaboCha やなんかの出力形式であるところの京大テキストコーパス形式のパーサモジュールを Perl で書いたので紹介します。GithubTarball on Github Ppagesこれを使うと例えば CaboCha の出力した係り受け関係を Perl のオブジェクトグラフとして取得できます。使用例単なる文節区切りの例。#!/usr/bin/env perl use v5.18; use utf8; use IPC::Open3; use Parse::KyotoUniversityTextCorpus; use Parse::KyotoUniversityTextCorpus::MorphemeParser::MeCab; use Symbol qw//; my ($in, $out, $err); my $pid; BEGIN { ($in, $out, $err) = (Symbol::gensym, Symbol::gensym, Symbol::gensym); $pid = open3($in, $out, $err, cabocha => '-f1'); } END { close $out; close $err; waitpid $pid => 0 if defined $pid; } binmode STDOUT, ':encoding(utf8)'; binmode $in, ':encoding(utf8)'; binmode $out, ':encoding(utf8)'; my $parser = Parse::KyotoUniversityTextCorpus->new( morpheme_parser => Parse::KyotoUniversityTextCorpus::MorphemeParser::MeCab->new, ); say $in '星から出るのに、その子は渡り鳥を使ったんだと思う。'; say $in '出る日の朝、自分の星の片付けをした。'; close $in; my $sentence_trees = $parser->…

Algorithm::LibLinear の紹介

Notice: This article is outdated. Please refer an updated English tutorial. 要旨かなり前になりますが、Algorithm::LibLinear という Perl モジュールを書きました。CPANGithubこれを使うと線形分類器などが高速に学習できます。テキストや画像の分類が応用として期待されます。LIBLINEAR についてLIBLINEARLIBSVM と同じ台湾国立大学の Chih-Jen Lin 教授のチームが公開しているオープンソースの機械学習パッケージです。 関数のロジスティック回帰、サポートベクター回帰及び線形 SVM による多クラス分類を行うことができます。LIBSVM と違ってカーネル関数を使うことはできませんが、はるかに高速に動作します。Algorithm::LibLinear についてLIBLINEAR には C++ で書かれたライブラリと、その機能を使って機械学習と分類・関数回帰を行うコマンドラインユーティリティが含まれています。 Algorithm::LibLinear はライブラリの機能を Perl からオブジェクト指向的に利用できるようにした上で、コマンドラインユーティリティの一部機能をライブラリ化して Perl で再実装したものです。使い方分類問題を解くときは、訓練データセットの読み込み・スケーリング学習器パラメータの設定分類器の訓練実データの分類という手順で行います。訓練データセットの読み込み正解ラベルのついたデータを大量に用意して学習させます。LIBSVM 形式のデータを読み込むか:my $data_set = Algorithm::LibLinear::DataSet->load(string => <<'EOD'); 1 1:0.1 2:0.1 4:0.1 -1 1:0.1 2:-0.1 3:0.1 ... EOD HashRef として表現されたデータを使います:my $data_set = Algorithm::LibLinear::DataSet->new(data_set => [ +{ feature => +{ 1 => 0.1, 2 => 0.1, 4 =…

OCaml で Web フロントエンドを書く

要旨フロントエンド開発に Elm は堅くて速くてとても良いと思う。昨今の Flux 系アーキテクチャは代数的データ型と相性が良い。ところで工数を減らすためにはバックエンドも同じ言語で書いてあわよくば isomorphic にしてしまいたいところだが、Elm はバックエンドを書くには現状適していない。OCaml なら js_of_ocaml でエコシステムを丸ごとブラウザに持って来れるのでフロントエンドもバックエンドも無理なく書けるはずである。まず The Elm Architecture を OCaml で実践できるようにするため Caelm というライブラリを書いている。俺の野望はまだまだこれからだ (未完)Elm と TEA についてElm というプログラミング言語がある。いわゆる AltJS の一つである。 ミニマリスティクな ML 系の関数言語で、型推論を持ち、型クラスを持たず、例外機構を持たず、変数の再代入を許さず、正格評価され、代数的データ型を持つ。 言語も小綺麗で良いのだが、何より付属のコアライブラリが体現する The Elm Architecture (TEA) が重要である。TEA は端的に言えば Flux フロントエンド・アーキテクチャの変種である。同じく Flux の派生である Redux の README に TEA の影響を受けたと書いてあるので知っている人もいるだろう。 ビューなどから非同期に送信される Message (Redux だと Action) を受けて状態 (Model; Redux だと State) を更新すると、それに対応して Virtual DOM が再構築されビューがよしなに再描画され人生を書き換える者もいた——という一方向の流れはいずれにせよ同じである。 差異はオブジェクトではなく関数で構成されていることと、アプリケーション外部との入出力は非同期メッセージである Cmd / Sub を返す規約になっていることくらいだろうか。後者は面白い特徴で、副作用のある処理はアプリケーションの外で起きて結果だけが Message として非同期に飛んでくるので、内部は純粋に保たれる。つまり Elm アプリケーションが相手にしないといけない入力は今現在のアプリケーションの完全な状態である Model と、時系列イベントである Me…