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

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

コメント

このブログの人気の投稿

C の時間操作関数は tm 構造体の BSD 拡張を無視するという話

久しぶりに C++ (as better C) で真面目なプログラムを書いていて引っかかったので備忘録。 「拡張なんだから標準関数の挙動に影響するわけねえだろ」という常識人は読む必要はない。 要旨 time_t の表現は環境依存 サポートしている時刻は UTC とプロセスグローバルなシステム時刻 (local time) のみで、任意のタイムゾーン間の時刻変換を行う標準的な方法はない BSD / GNU libc は tm 構造体にタイムゾーン情報を含むが、tm -> time_t の変換 ( timegm / mktime ) においてその情報は無視される 事前知識 C 標準ライブラリにおいて時刻の操作に関係するものは time.h (C++ では ctime) ヘッダに定義されている。ここで時刻を表現するデータ型は2つある: time_t と tm である。time_t が第一義的な型であり、それを人間が扱い易いように分解した副次的な構造体が tm という関係になっている。なので標準ライブラリには現在時刻を time_t として取得する関数 ( time_t time(time_t *) ) が先ずあり、そこから time_t と tm を相互に変換する関数が定義されている。 ここで time_t の定義は処理系依存である。C / C++ 標準はそれが算術型であることを求めているのみで (C11 からは実数型に厳格化された)、その実体は任意である。POSIX においては UNIX epoch (1970-01-01T00:00:00Z) からのうるう秒を除いた経過秒数であることが保証されており Linux や BSD の子孫も同様だが、この事実に依存するのは移植性のある方法ではない。 一方で tm は構造体であり、最低限必要なデータメンバが規定されている: int tm_year : 1900 年からの年数 int tm_mon : 月 (0-based; 即ち [0, 11]) int tm_mday : 月初からの日数 (1-based) int tm_hour : 時 (Military clock; 即ち [0, 23]) int tm_min : 分 int tm_sec : 秒 (うるう秒を含み得るので [0

開発環境の構築に asdf が便利なので anyenv から移行した

プロジェクト毎に異なるバージョンの言語処理系やツールを管理するために、pyenv や nodenv など *env の利用はほとんど必須となっている。 これらはほとんど一貫したコマンド体系を提供しており、同じ要領で様々な環境構築ができる非常に便利なソフトウェアだが、それを使うことで別の問題が出てくる: *env 自身の管理である。 無数の *env をインストールし、シェルを設定し、場合によりプラグインを導入し、アップデートに追従するのは非常に面倒な作業だ。 幸いなことにこれをワンストップで解決してくれるソリューションとして anyenv がある。これは各種 *env のパッケージマネージャというべきもので、一度 anyenv をインストールすれば複数の *env を簡単にインストールして利用できる。さらに anyenv-update プラグインを導入すればアップデートまでコマンド一発で完了する。素晴らしい。 そういうわけでもう長いこと anyenv を使ってきた。それで十分だった。 ——のだが、 ここにもう一つ、対抗馬となるツールがある。 asdf である。anyenv に対する asdf の優位性は大きく2つある: 一貫性と多様性だ。 一貫性 “Manage multiple runtime versions with a single CLI tool” という触れ込み通り、asdf は様々な言語やツールの管理について一貫したインタフェースを提供している。対して anyenv は *env をインストールするのみで、各 *env はそれぞれ個別のインタフェースを持っている。 基本的なコマンド体系は元祖である rbenv から大きく外れないにしても、例えば jenv のように単体で処理系を導入する機能を持たないものもある。それらの差異はユーザが把握し対応する必要がある。 多様性 asdf はプラグインシステムを持っている。というより asdf 本体はインタフェースを規定するだけで、環境構築の実務はすべてプラグイン任せである。 そのプラグインの数は本稿を書いている時点でおよそ 300 を数える。これは言語処理系ばかりでなく jq などのユーティリティや MySQL のようなミドルウェアも含むが、いずれにしても膨大なツールが asdf を使えば

macOS で GUI 版 Emacs を使う設定

macOS であっても端末エミュレータ上で CLI 版 Emacs を使っているプログラマは多いと思うが、端末側に修飾キーを取られたり東アジア文字の文字幅判定が狂ってウィンドウ描画が崩れたりなどしてあまり良いことがない。 それなら GUI 版の Emacs.app を使った方がマウスも使える上に treemacs などはアイコンも表示されてリッチな UI になる。 しかし何事も完璧とはいかないもので、CLI だと問題なかったものが GUI だと面倒になることがある。その最大の原因はシェルの子プロセスではないという点である。つまり macOS の GUI アプリケーションは launchd が起動しその環境変数やワーキングディレクトリを引き継ぐので、ファイルを開こうとしたらホームディレクトリ ( ~/ ) でなくルートディレクトリ ( / ) を見に行くし、ホームディレクトリなり /opt/local なりに好き勝手にインストールしたツールを run-* 関数やら shell やら flycheck やらで実行しようとしてもパスが通っていない。 ワーキングディレクトリに関しては簡単な解決策があり、 default-directory という変数をホームディレクトリに設定すれば良い。ただし起動時にスプラッシュスクリーンを表示する設定の場合、このバッファのワーキングディレクトリは command-line-default-directory で設定されており、デフォルト値が解決される前に適用されてしまうので併せて明示的に初期化する必要がある: (setq default-directory "~/") (setq command-line-default-directory "~/") 次にパスの問題だが、まさにこの問題を解決するために exec-path-from-shell というパッケージがある。これを使うとユーザのシェル設定を推定し、ログインシェルとして起動した場合の環境変数 PATH と MANPATH を取得して Emacs 上で同じ値を setenv する、という処理をやってくれる。MELPA にあるので package-install するだけで使えるようになる。 このパッケージは GUI

js_of_ocaml の使い方

js_of_ocaml (jsoo) は Ocsigen が提供しているコンパイラである。その名の通り OCaml バイトコードから JavaScript コードを生成する。 これを使うことで OCaml で書いたプログラムを Web ブラウザや node.js で実行することができる。 インストール 単に OPAM を使えば良い: $ opam install js_of_ocaml js_of_ocaml-ocamlbuild js_of_ocaml-ppx バージョン 3.0 から OPAM パッケージが分割されたので、必要なライブラリやプリプロセッサは個別にインストールする必要がある。 とりあえず使うだけなら js_of_ocaml と js_of_ocaml-ppx の二つで十分。後述するように OCamlBuild でアプリケーションをビルドするなら js_of_ocaml-ocamlbuild も入れると良い。 これで js_of_ocaml コマンドがインストールされ、OCamlFind に js_of_ocaml 及びサブパッケージが登録される。 コンパイルの仕方 以下ソースファイル名は app.ml とし、ワーキングディレクトリにあるものとする。 手動でやる場合 一番安直な方法は、直接 js_of_ocaml コマンドを実行することである: $ # バイトコードにコンパイルする。js_of_ocaml.ppx は JavaScript オブジェクトの作成や操作の構文糖衣を使う場合に必要 $ ocamlfind ocamlc -package js_of_ocaml,js_of_ocaml.ppx -linkpkg -o app.byte app.ml $ # 得られたバイトコードを JavaScript にコンパイルする $ js_of_ocaml -o app.js app.byte OCamlBuild を使う場合 OCamlBuild を使う場合、.js 用のビルドルールを定義したディスパッチャが付属しているので myocamlbuild.ml でこれを使う: let () = Ocamlbuild_plugin . dispatch Ocamlbuild_js_of_ocaml . dispatcher $ # app.ml ->

私家版 TypeScript 抽象データ型表現

TL, DR; 読んだ: TypeScriptの異常系表現のいい感じの落とし所 | Developers.IO 方向性はとても同意できるがデータがオブジェクトである積極的な理由がないのが分かる。今日び new Success(...) もあるまい。 構造的型付が原則なんだから Namespace Import する前提で型定義と関数を公開してしまった方が単純な FP スタイルで書けて勝手が良い。 そういうわけで僕なら こう書く。 使い方 import * as Result from './result' ; function doSomethingFailable () : Result . T < number , Error > { const r = Math . random () ; return r < 0.5 ? Result . success (r) : Result . failure ( new Error( 'Something failed.' )) } function orDefault < V > (result : Result . T < V , unknown >, defaultValue : V) : V { return Result . match (result , { failure() { return defaultValue ; } , success(value) { return value ; } , }) ; } const result = doSomethingFailable() ; console . log (orDefault(result , NaN)) ; // Prints a number < 0.5, or NaN. 自明な flatMap / map がないのでより低水準な変換として match を提供しているが、もちろん型の利用者が合意できるなら Optional に類する定義を採っても良い: function map < V , U