変数は「箱」か?

ここ数日、業務が落ち着く頃合いを見計らっては週末に行われるPerl入学式の資料に手を入れていました。

資料は校長の @papix さんがざーっと一気に作ってくれて、ぼくがやったのはちょこちょこっとしたリファクタリングというか、文字校正みたいなものでしたが、それだけでもいろいろ勉強になって楽しかったです。
(あいにく、ぼくは当日参加できないのですが)

さて、その資料を作る際にはメンバーの方々とあーでもないこーでもないと様々な議論をしていたのですが、その一つに「変数を『箱』に喩えて説明しているけど、これでいいのか?」という話がありました。

「箱」か「名札」か

プログラミング入門者に「変数」の概念をどう伝えるか、といったときに、そこでどんな喩えを用いるか、というのはなかなか伝統的なイシューだと思われるのですが、ぼく自身の結論から言うと、現代では「箱」よりは「名札」とするほうが妥当かなと思っています。

なぜなら、「箱」にモノを入れた場合、その箱を同時に使えるのは1回きりになるからです。

具体的な例をひとつ考えると、たとえば「ミスター」と書かれた箱に「長嶋茂雄」氏が入っているとします。
言うまでもなく、長嶋さんは世界に一人だけなので、その箱も一つしか作れません。

しかし実際には、変数というのは一度に何回も使えるものなので、

ミスターといえば巨人。ミスターといえば背番号3。ミスターといえばサード。ミスターといえば……

みたいに使いたいわけですが、この「ミスター」を箱として考えてしまうと、2つめ以降の「ミスター」に置ける「箱+長嶋さん」が存在せず、喩えとして破綻します。

言い換えれば、「箱」に喩えても成立する状況というのは、その変数を一回しか呼び出さないときだけです。

ミスターといえば巨人。以上!

一方、もしこれが「名札」であれば、このような破綻は起きません。
名札はいくらでも量産でき、実体(長嶋さん)は一つ存在すれば充分だからです。

よって、どちらが適切か、といえば「名札」の方だと思います。

入門書の現状

しかしながら、身の回りにある入門書を見ると、どちらかと言うと「箱」の例を使うもののほうが多いようです。

ざっと見ただけでも、以下がそうでした。

一方、「名札」を使っているものは以下でした。

これとほぼ同じ意味で、ドットインストールは「ラベル」と言っています。
(特定の言語によらないのでトップページにリンクしておきます)

また、とくに何かに喩えていないものもあります。

その他、ちょっと変わっているというか、さすがと思ったのは深沢千尋さんの以下の2冊。

こちらでは「中学校の数学で出てくるxとかyのようなもの」と説明されています。なるほど。

くだらない話?

ところで、上記の「箱」組の最後に挙げた前橋和弥さんの『センス・オブ・プログラミング!』では、変数を何に喩えるかというそのものズバリの話題があり、以下のように書かれていました。

本書に限らず、変数を「箱のようなもの」と説明している入門書は多いものです。
ただ、これは妥当なたとえではないという人もいます。その主張は、

変数が「箱のようなもの」で、その箱に値を入れることが「代入」であるとするなら、その「箱」からまた別の「箱」に代入を行ったとき、前の「箱」は空になってしまうはずではないか。

というものです。
これは確かに一理あります。
(略)
こう考えると、変数は「箱のようなもの」というより、「メモ用紙のようなもの」とでも考えたほうがよいのかもしれません。
(略)
しかし――変数が「箱」であるのか「メモ用紙」であるのか、そんなくだらない話を真剣に議論してもしょうがないでしょう。たとえ話は所詮たとえ話です。「変数」が実際にどのようなものかを知りたければ、結局、「メモリ」の話を抜きにすることはできないと思います。

(p33-34・太字原文ママ

この結論には、正直、残念な思いがしました。本書は非常に面白く、現時点を含めて数年にわたって少しずつ読み続けているのですが、それでもこのくだりの意見には賛同できません。

なぜなら、ここで「くだらない」と表現されているささいな矛盾に引っかかるのは、著者にイチャモンを付けたいだけの性格の悪い誰かではなく(まあ、そういう人もいるかもしれませんが)、同書が対象にしている、ぼくのような入門者だからです。

そのような入門者を示す良い例として、@uzulla さんがブログに書かれた以下の話があります。

実際に聞かれた事ですが、たとえばfor文は以下のようなものですが、$i、というのがよくわからないといわれました。

for($i=0;$i>10;$i++){
    echo $i;
}

どこの本にも$iとかいてあるので、これは$iでないといけないと勘違いする人とかいます。

「なぜ $i なんだろう?」という疑問は、非入門者にはなかなか湧いてこないものかもしれませんが、入門者はこういう「どうでもいいこと」にバリバリ引っかかります。

「さっきまで $foo だった変数名がなぜ今度は $hoge なんだろう? 何か意味があるのだろうか?」とか思います。

そういう状況が生じてしまうのは、教える側が「そんなことはどうでもいい」と思っているがゆえに、その点をテキトウに説明してしまっているからです。

逆に、もし教える側が「そうそう、こういう細かいところで初心者は引っかかるんだよな〜」ということをわかっていて、またそうした疑問を尊重していれば、変数名を付ける際に foo と hoge を無規則に混在させるようなことはしないでしょうし、上のうずらさんの例で言えば、「ここにある $i というのはとくに意味はありません。$j でも $k でも $abcdefg でもいいです。でも、 i が打ちやすいのでそうしておきます」といった説明を事前にできるかもしれません。
教わる側としても、先にそれを聞いていればわざわざそんな質問をする必要はなくなります。

ここで言いたいことは、そういったことを「くだらない」と思うのは教える側でしかなく、教わる側にはそれがくだらないかどうかすらわからない、ということです。

そして、もし最初から疑問が生じづらい(矛盾の少ない)喩えを用意できていれば、それによって教える側も教わる側も、貴重な時間や労力を節約できるはずだということです。

直感的かどうか

さてしかし、ここまで「名札」推しのぼくでしたが、冒頭で紹介したPerl入学式の最新の講義資料では、それまで採用されていた「箱」の例を直しませんでした。(よって今も「箱」のまま)

その理由は、第一には「面倒だから」で、何が面倒なのかというと、この資料は基本的に複数人の合議のもとに作られているので(資料の作成メンバーは東京だけでなく大阪にもいて)、あまりホイホイ自分勝手に変えられない、ということがあります。

変えられないと言っても、べつにメンバー間の雰囲気が悪くて提案できない、といったことではなく、実際には今回も「どっちかというと箱より名札のほうがいいし、今後のトレンドはそうなっていくと思う」などと偉そうなことを普通に伝えましたし、メンバーもまたそうした意見に耳を傾ける度量を持ってはいるのですが、それでも意思決定にかかる時間が少し負担になりそうだったので、無難に現状維持を選択しました。

逆に言うと、もしこれがぼく個人の文責によるものなら、迷わず「名札」にしたと思います。

そして、直さなかった理由はもう一つあって、それは「箱」のほうが直感的というか、映像的に状況をとらえやすい、と感じるからです。

ぼく自身、入門時期には上に列記したような「箱」に喩えている本を通して変数の雰囲気を把握しましたし(とくに結城浩さんの本ではイラストが明快で直感的だった)、確かにその喩えはある種の人の頭の中ではゆくゆく破綻していきますが、同時にそれは致命的な破綻でもなく、学習に支障をきたすようなものではないとも思います。

言い換えれば、一時的に必要なハシゴとして、とりあえずその時に用が足りる道具として「箱」を使うのはアリかなと思っています。

まとめ

え、じゃあ結局何に喩えればいいと思ってるの? と聞かれたら、もし「箱」とそれ以外の何かから選ぶのであれば「名札(ラベル)」がいいと思います。

しかし一方で、そもそも何かに喩える必要があるのか? という自問も必要だと思います。

たとえば、上に挙げた『かんたんPerl』や、同著者による以下の本では、

変数の説明の最中にいきなりパソコンのメモリ(実物)の写真を登場させて、「この中にはトランジスターがびっしり植えられていて……変数を用意するとそのぶんの領域を確保してくれて……」みたいな大変具体的な話が展開されていたりします。
喩える気ゼロ。という気もしますが、これもまた一つの解だと思います。

変数は「箱」か? という問いに対しては、そうとも言えるし、そうじゃないとも言えます。

しかしいずれにせよ、大切なことは、「それが入門者にとって効果的なのかどうか」を考え続けることだと思います。
「とりあえずこんな風に説明しておけばいい」という標準的な例を洗練させながら、最終的には、その瞬間に目の前にいる「教わる人」に最適な説明を考えていくべきでしょう。

*1:手元にあるのは第3版なので厳密にはこの第5版については不明です。

最近のC言語に関するVimシンタックスチェック状況

以下の記事で紹介したC言語シンタックスチェックの方法について、続報。

note103.hateblo.jp

その段階では、このように記述していたのだけど。

Plug 'rhysd/vim-clang-format'

let g:clang_format#code_style = 'WebKit'
let g:clang_format#style_options = {
            \ "AccessModifierOffset" : -4,
            \ "AllowShortIfStatementsOnASingleLine" : "true",
            \ "AlwaysBreakTemplateDeclarations" : "true",
            \ "Standard" : "C++11",
            \ "BreakBeforeBraces" : "Stroustrup",
            \ }

nnoremap <Leader>ct :ClangFormat<CR>

現在はこのような感じになっている。

Plug 'rhysd/vim-clang-format'

let g:clang_format#code_style = 'LLVM'
let g:clang_format#style_options = {
            \ "AccessModifierOffset" : -4,
            \ "AllowShortIfStatementsOnASingleLine" : "true",
            \ "AlwaysBreakTemplateDeclarations" : "true",
            \ "Standard" : "C++11",
            \ "BreakBeforeBraces" : "Stroustrup",
            \ }

nnoremap <Leader>ct :ClangFormatAutoToggle<CR>

差分。

-let g:clang_format#code_style = 'WebKit'
+let g:clang_format#code_style = 'LLVM'

-nnoremap <Leader>ct :ClangFormat<CR>
+nnoremap <Leader>ct :ClangFormatAutoToggle<CR>

どうやら今参考にしている本の書式は、WebKit の方ではなく LLVM というものが近いみたいだったので、それに変更。

それから、:ClangFormat というのはわざわざ打たなくても保存するたびに勝手に整形してくれるので&逆にそれがけっこう煩わしくなるケースもあって、一時的にチェックを解除できるようにオン・オフのトグルを使えるようにした。

これでまた一つ恒常的な学習を行える環境に近づけた。

VimでMarkdownをカンタンにHTML化する

note103.hateblo.jp

以前に上の記事を書いたのは、もう1年以上前のこと。だから、というわけでもないのだけど、少し方法がアップデートされているので差分を記してみておきます。

まず、以前の方法を簡単にまとめると、

  1. Markdown記法で何か書く。
  2. 自作の md2html.pl で変換。
  3. HTML化された文書の出来上がり。

というもので、シェル上のコマンドで言うと、こんな感じ*1

$ perl md2html.pl in.md > out.html

それがその後、Perl界の先輩である @karupanerura さんから、それだったらこう書けるよ! と教えて頂きまして。

$ cat in.md | Markdown.pl > out.html

つまり、わざわざそういうスクリプトを使わなくても、Perlの Text::Markdown モジュールをインストール済みなら Markdown.pl コマンドを使えるので、それを通せば変換できるよ、と。

それで、その後はありがたく1年ほどその方法にて変換作業をしていたのですが、ふとこれ、Vim のほうにショートカットを仕込めるはずだなあ、と思いまして。

以下のようにマッピングしてみました。

nnoremap <Leader>mh :%! Markdown.pl %<CR>

使っているところ。

f:id:note103:20160605153705g:plain

変換後に、Vimプラグインの previm を使ってブラウザで確認しています。

で、とりあえずこれだけでもOKといえばOKなのですが、僕の性格的にはソースのファイル(バッファ)を上書きしてしまうのは結構抵抗があるので、変換後のHTMLは別のバッファに保存して、元のファイルは 残しておけるように以下のような関数を vimrc に設定してみました。

function! s:Markdown2HTML()
  execute ":%y"
  execute ":new"
  execute ":0put +"
  execute ":w ".strftime('%Y-%m-%d-%H-%M').".html"
  execute ":%! Markdown.pl %"
  execute ":w"
endfunction
nnoremap <Leader>mh :<C-u>call <SID>Markdown2HTML()<CR><CR>

ん〜む……なんだかむっちゃ冗長感溢れていますが、リファクタリングはまた学習の進度に沿ってということで……。
とりあえずやっていることとしては、

":%y" <= バッファ全体をコピー
":new" <= 新規バッファを水平分割で作成
":0put +" <= コピーしておいたテキストを新規バッファへペースト
":w ".strftime('%Y-%m-%d-%H-%M').".html" <= 新規バッファを現在日時分秒を元にしたファイル名で保存(この時はまだMarkdown書式)
":%! Markdown.pl %" <= 保存したバッファでテキストをMarkdownからHTMLへ変換
":w" <= HTMLファイルを保存

という感じです。

動いている様子は、こんな。

f:id:note103:20160605153635g:plain

便利!!

成果

さて上記でサンプル的に使ったMarkdownファイルですが、じつは本日パブリッシュしたこちらの記事でした。

perl-entrance.blog.jp

冒頭に示した記事でも書いたように、Perl入学式では各回のレポートを参加したサポーターさんに書いてもらって、その校正&ブログUPは可能な範囲でぼくが担当したりしています。

この新たに手に入れた Vim & Perl ワザを使って、今後ますます同ブログをカンタンに更新していけそうです。

*1:厳密にはちょっと当時の設定と違うのだけど、実際の構造を把握しやすいように少しアレンジした。

Perlで配列を1行ずつカンタンに出力したい

Perlの小ネタシリーズ。

と言いつつ、文章はそこそこ長いです。

課題

さっそくですが、以下のような配列があるとして、

my @colors = qw/red blue orange green yellow/;

このまま出力すると、

#!/usr/bin/env perl
use strict;
use warnings;
use feature 'say';

my @colors = qw/red blue orange green yellow/;

say @colors;

こんな感じになる。

redblueorangegreenyellow

これだとなんだかわからないので、要素間に改行を入れて、以下のように配列の中身を見やすくしたい、ということがよくある。

red
blue
orange
green
yellow

そのための方法として、初心者的に最初に思いつくのは、これとか。

for (@colors) {
    say $_;
}

あるいはこれとか。

say join "\n", @colors;

あるいは最初のやつを後置にするとか。

say $_ for (@colors);

で、これまではこういう場合、一番よく使うのは2番めの join を使う方法だったのだけど、昨日ふと思ったのが「いや、面倒すぎるだろ」ということで。

たしかに一回一回はさほどのタイプ数でもないのだけど、チリも積もればというやつで、これを一生繰り返し続けたら一体何タイプになるのだ、と。

というか、その join 方式にしても地味にダブルクオートやバックスラッシュなども入っていて、案外コストは高いとも言えるし。
今まで見てみぬふりをしてきたけど、これはちょっと改善しないとマズイよなあ……と。

で、普通こういうときというのはすでに誰かが同じことを考えていて、かつすでに解決策を講じてくれていて、さらにはすでにその方法を広く公開・共有してくれていたりするのだけど、寡聞にしてぼくはその辺のことをよく知らない。
皆さん、どうしてるんでしょうね? こういうの。

とりあえずなんとなーく、おぼろげな記憶というか印象として、あれ、こういうモジュールあったような……と思って、List::Util というのを当たってみたのだけど、
List::Util - search.cpan.org

ん〜、機能はむっちゃ充実しているのだけど、そして今あらためて見ながら「うわ、こんな機能あったんだ…… unique とか今度からはこれ使おう」などと思ったのだけど、当の求める機能はなさそう。(たぶん)

ちなみに、同モジュールについては以下に日本語での平易な解説があって、大変参考になりました。

検討

さて、ではあらためて、それならどうすればいいのか。

もしこれがターミナルから使うbashのコマンドとかだったら、これまでに結構似たようなことをやってきているので(ディレクトリ移動やファイルのリネームをするツールなど)、その応用でいけそうな気もするのだけど、今回はちょっと毛色が違うのでどうしたらいいのだか……。

それで、パッと思ったのはとりあえず自分で専用のモジュールを書いて、毎回それを use などで読み込んで使えるようにする、ということかなと。

もちろん、というか特定のモジュール化をしてしまうと、それを使ったコードは汎用性が低くなるというか、他の人とコードの共有をしづらくなってしまう可能性が高まるのだけど、ここでの目的はあくまで「配列を手軽に改行しながら表示したい!」という、個人的かつ一時的なデバッグ的な欲望にもとづくものなので、その限りにおいては「自分しか使わないモジュール」に依拠してしまってもとりあえず構わないかな、と。

ただし、それでも残る懸念としては、今回のモジュールは特定のファイル内でのみ使うものではなくて、コンピュータ内のどこでPerlを書いても使えるようにしたいという前提があるので、たぶん例の @INC 問題でハマる可能性がかなり高そう……

ということで、これまで幾度となく挑んでは敗れてきた @INC 系の情報に関してもあちこち調べて回った末、とりあえず至った結論は以下のようなものでした。
時系列で記してみます。

試行

とりあえずモジュール書く

まず、今回求めている機能は非常にシンプルなもので、こんな感じ。

sub foo {
    my @foo = @_;
    for (@foo) {
        say $_;
    }
}

んで、使うときには、

foo @colors;

とかする。だいぶシンプル!

名前を決める

さてそれで、そのとりあえず「foo」としているところ、つまりモジュール名、および関数名をどうするか……

命名の条件としては、そもそもタイプ数を少なくしたいので数文字で固めたい。
次に、内容をある程度想起させるもので。

今回の場合、配列に改行を挟みたいということなので、キャリッジリターンこと「cr」とか。あるいは「改行」で辞書を見ると「newline」とあるので「nl」とか?

ん〜、しかし「cr」は若干打ちづらいし、「nl」はそれよりは打ちやすいけど、見栄えがパッとしない……ああ、じゃあ「n」とか?

ということで、モジュールはこんな感じに。

n.pm
package n;
use strict;
use warnings;
use feature 'say';
use parent 'exporter';

our @export = qw(n);

sub n {
    my @n = @_;
    for (@n) {
        say $_;
    }
}

1;

はい。モジュール(パッケージ)名も関数名も「n」にしました。

初めてのエクスポート

上のコードで、自分的には初めてやったことがひとつあります。

use parent 'Exporter';
our @EXPORT = qw(n);

この2行を入れることによって、本来なら

n::n @colors;

としなければならないところが

n @colors;

というふうに関数呼び出しのみで済むようになっている。

この辺の情報は、平松さん( id:kaz_hiramatsu )の『雅なPerl入門 第3版』で学びました。(P120-121)
kazhiramatsu.hatenablog.com

Expoter モジュールや @EXPORT はいまだに全然理解できていないのだけど、やりたいことはできつつあるのでひとまずヨシ。

Minilla

では次。上のモジュールを書いただけだと、それを use しても読み込んでくれない&上記のとおり、今回は特定のディレクトリ内だけではなく、コンピュータ上のどこでPerlのコードを書いてもそのモジュールを使えるようにしたいので、たぶん通常のCPANモジュールをインストールするようなことをしなきゃいけない気がする。

しかし、どうやってそのモジュールを「インストール」すればいいのか……知らない。わからない。

ということで、これはけっこう調べる&理解するのに時間がかかったのだけど、最初に試したのは Minilla で。

この Minilla についても、上記の『雅なPerl入門』で詳しく解説されていてとても参考になりましたが、とりあえずこれを使って、

$ minil new n

で新規のレポジトリを作って、そこに上記の n.pm を入れて、同ディレクトリ内で

$ minil install

としたら……使えた!!

のですが、なんか違う……いや、ちゃんと動くのは動いたので、それで解決って言いたい気もするのだけど、第一にMinillaってそういうためのソフトウェアではなさそうだし、第二に(違和感としてはこっちのほうが重要だったのだけど)、今回やりたいことをやるためとしてはちょっと大げさすぎるような。

ということで、ひとまずやりたいことは実現できたものの、別の方法を探してもうしばらく旅を続ける……

環境変数 PERL5LIB を使う

それで訪れた次の試行。この章題は、以下の記事中からいただきました。
qiita.com

いつもお世話になっています @xtetsuji さんのQiita記事ですね。

そしてそれに近いネタとして、ほぼ同時点でたまたま検索して辿りついたのが以下。
perl-users.jp

じつはというか、前者のてつじさんの記事も、後者の記事にインスパイアされて&アップデート的な意味も含めて書かれたもののようで、だからというか相乗効果的にお二方の言わんとすることを理解しやすかったように思います。

それでその、肝心の目的の実現方法についてですが、どうやら任意のモジュールを読み込み可能な状態とする(@INC の中にそのモジュールを追加する)ためには、いくつかの選択肢があって、そのひとつとして、「PERL5LIB」という環境変数に、任意のパスを追加すればいいという。

正直なところ、ぼくはこのパス(PATH/環境変数)というやつもまともに理解できていなくて、しばらく前に読んだ良書『新しいLinuxの教科書』でようやくその一端に触れられた感じはあるのだけど、

新しいLinuxの教科書

新しいLinuxの教科書

とはいえ、まだあんまり腑に落ちてはいない。

いないのだけど、とりあえず bash_profile にこんなふうに記入したうえで、

export PERL5LIB=/path/to/n.pm

(実際には「/path/to/」の部分にはモジュールまでのディレクトリパスを書いている)

source コマンドでリロードすると、

source ~/.bash_profile

なんとモジュールを読み込むようになった。

以下、その模様。

f:id:note103:20160605004050g:plain

キタ!!!

オチ

さて、ここまでやってくる中で、初めはたんに「いちいち改行処理するのがメンドイ!」というだけの怠惰なモチベーションに突き動かされて旅に出てはみたものの、その過程で様々な思いがけない知見を得ることができまして、やはり学習楽しい! プログラミング楽しいな! と思うばかりだったのですが、その途上でひとつ気づいてしまったのが、上述の @xtetsuji さんの記事中にて、以下のような記述がありまして。

Debian wheezy のシステムPerl (最初からシステムに入っていたり、ディストリビューション公式のパッケージとして提供されているPerl) では以下のようなコマンドを打つと、以下のような出力が返ってきます

$ perl -E 'say for @INC'
/etc/perl
/usr/local/lib/perl/5.14.2
/usr/local/share/perl/5.14.2
/usr/lib/perl5
/usr/share/perl5
/usr/lib/perl/5.14
/usr/share/perl/5.14
/usr/local/lib/site_perl
.

ふむふむ……ん? このコード部分の1行目、

$ perl -E 'say for @INC'

これって、もしかすると……

my @colors = qw/red blue orange green yellow/;

say for @colors;  #<= 入れてみた

実行。

red
blue
orange
green
yellow

ああ〜。

for文の頭に、joinどころかスカラー変数すら置かずに「say」するだけでいいんだ〜……へえ〜……。
もしも最初にこれを知っていたら、ぼくたぶんこの試み一切やってなかったですね(笑)。

上のGIF動画を作りながらも思っていましたが、いくら関数名やモジュール名が1文字でラクだと言っても、いちいちファイルごとに use するのはやはり普通に手間だし。

まあ、とはいえ、「自作モジュールをひとまず自分だけで自由に使うにはどうすればいいか」という問題に対しては大きな前進を遂げることができたので、それでヨシとします……!

付録

元々は想定していなかったけど、当然のようにスカラー変数も出力できますね。

my $foo = 'bar';
n $foo;

f:id:note103:20160605093532g:plain

Cool!

って、自分だけが見る前提のプリントデバッグぐらいでしか使えませんが……。

Perlの正規表現で後ろからマッチさせる

わかってみると単純ながら、けっこうハマったのでメモ。

以下のような文字列があるとして、

my $fruits = '/apple/orange/grape/lemon/banana/';

このうち「lemon」の一つ前にある果物だけを取り出したい、とする。

ただし、上記ではその果物が「grape」であり、その前の果物が「orange」であることが明らかだけど、設問上はそこは不明確というか、可変で、さらに言えばその前の「apple」の前にもまだ他の果物がどんどん入ってくるかもしれない、とする。

つまり確実にわかっているのは、「lemonの一つ前にある果物を取り出したい」ということだけ。

となると、「orange」と「lemon」で対象を挟むことはできないし、前から数えていくつめ、と指定することもできない。
よって「lemon」のほう、後ろからマッチさせる方向で考える。

で、最初はこういうのを書いたんだけど、

#!/usr/bin/env perl
use strict;
use warnings;
use feature 'say';

my $fruits = '/apple/orange/grape/lemon/banana/';

if ($fruits =~ /\/(.+)\/lemon/) {
    say $1;
}

それだと、結果はこうなる。

apple/orange/grape

いわゆる最長マッチ、欲張りなマッチ状態ですね。

んで、じゃあ最短マッチ(非欲張りマッチ)ってどうやるんだっけ、と思ってこのようにやると、

my $fruits = '/apple/orange/grape/lemon/banana/';

if ($fruits =~ /\/(.+?)\/lemon/) {  #<= 「+」の後に「?」を追加
    say $1;
}

実行。

apple/orange/grape

変わらない。そういう問題ではなかったみたい。

ここまで来て、ん〜あれ、正規表現って後ろからマッチしていくこと自体そもそもできないんだっけ・・?? とかしばらく考え込んでしまったのだけど、結論としてはこんな。

my $fruits = '/apple/orange/grape/lemon/banana/';

if ($fruits =~ /.+\/(.+)\/lemon/) {
    say $1;
}

実行。

grape

はい。
ということで、パターンの頭に「.+」を付けただけでした。「.*」でも構わない。

またひとつ ぱーる の どうぐ を てにいれた! という感じでした。

C言語の航海日誌(4)

参考書の通読や写経も続けていますが、今日はちょっと寄り道というか予習というか、そんな感じでポインタの自習を。

たとえば、こんなコードがあったとして。

fruits.c
#include <stdio.h>

int main(void)
{
    int apple = 3;
    int lemon = 8;

    printf("リンゴ: %d\n", apple);
    printf("レモン: %d\n", lemon);

    return 0;
}

こんな結果になる。

リンゴ: 3
レモン: 8

はい。

で、今回のテーマであるポインタ、というのはどういう時に使うかっていうと、同じ値を複数の関数の間で渡したりするときに使う。という認識が僕にはあるので、上記をポインタの練習用にアレンジすると、たとえばこんな。

fruits_practice.c
#include <stdio.h>

void practice(int *apl, int *lmn)
{
    printf("リンゴ: %p\n", apl);
    printf("レモン: %p\n", lmn);

    printf("*リンゴ: %d\n", *apl);
    printf("*レモン: %d\n", *lmn);
}

int main(void)
{
    int apple = 3;
    int lemon = 8;

    practice(&apple, &lemon);

    return 0;
}

結果。

リンゴ: 0x7fff54bad4f8
レモン: 0x7fff54bad4f4

*リンゴ: 3
*レモン: 8

はい。やってることとしては、main関数のほかにpractice関数というのを作って、そっちに「&apple, &lemon」という形で、appleと lemonのアドレスを渡している。
「アドレス」というのは、ここでは「0x7fff54bad4f8」とか出ている、そういうやつ。

ちなみに、ポインタの練習といえば通常、そのアドレスを渡した先(ここではpractice関数)で数値を入れ替えてみたりするわけだけど、この段階ではまだやらない。
まずはそれ以前の確認をきちんとしたいから。

さて、ポインタ関連の説明に触れ始めた頃、こうした構造というか状況を見て少し混乱したのは、このpractice関数のほうで引数を受け取る(=変数を宣言する)ときに、

void practice(int *apl, int *lmn)

というふうにしているんだけど、この「*apl, *lmn」というのはその後のプリントで「3, 8」と出ていることからもわかるように、アドレスではなく数値というか、値である。

つまり、main関数から投げた段階ではアドレスが示されているのに、それを受け取ったときにはアドレスではない「値」が入っているようなので、なぜ&どこで、それが変わってるの? という疑問を持った。
直感的には、受け取る側のほうでも、送る側と同じ「アドレス」をもらっているように見えると腑に落ちやすいのだけど。

具体的にいうと、現在このようになっているところが、

void practice(int *apl, int *lmn)
{
    printf("リンゴ: %p\n", apl);
    printf("レモン: %p\n", lmn);

    printf("*リンゴ: %d\n", *apl);
    printf("*レモン: %d\n", *lmn);
}

こんなふうになっている、とか。

void practice(int &apl, int &lmn) // <= & でアドレスを受け取る
{
    printf("リンゴ: %p\n", &apl); // <= & でアドレスを表示
    printf("レモン: %p\n", &lmn);

    printf("*リンゴ: %d\n", apl); // <= 無印で値を表示
    printf("*レモン: %d\n", lmn);
}

あるいは、こんな。

void practice(int apl, int lmn) // <= 無印でアドレスを受け取る
{
    printf("リンゴ: %p\n", apl); // <= 無印 でアドレスを表示
    printf("レモン: %p\n", lmn);

    printf("*リンゴ: %d\n", *apl); // <= * で値を表示
    printf("*レモン: %d\n", *lmn);
}

しかし実際はこうなっている。

void practice(int *apl, int *lmn) // <= * で値を受け取る
{
    printf("リンゴ: %p\n", apl); // <= 無印 でアドレスを表示
    printf("レモン: %p\n", lmn);

    printf("*リンゴ: %d\n", *apl); // <= * で値を表示
    printf("*レモン: %d\n", *lmn);
}

最後の(そして実際の)状態でも受け取った後の整合性は取れているのだけど、受け取る瞬間がなんか「ん〜?」という感じではある。

ただし、確かに上の素朴な代案にしてもやはり問題はあって、最初の代案だとポインタ型の変数と通常のint型の変数との違いが見てわからないし、後者の案だと受け取っている段階の見え方がこれまた通常のint型の変数(仮引数)の宣言とまったく変わらないので、それはそれで問題ありそう。

まあこれはこれで妥当な落としどころなのかなあ、と思うけれど……。(とくにオチや結論はない)

さて元の例(fruits_practice.c)に戻ってもう少し理解の確認を進めたい。
このコードでは、まず二つある関数のうち下のmain関数のほうで、「apple」に3、「lemon」に8を代入している。

そして、それらをpractice関数に投げるときに、アドレス「&apple, &lemon」に切り替えて渡している。

さらに、それらを受け取る側では、「int *apl, int *lmn」というふうに受け取っていて、このときに「*apl」と「*lmn」の中に入っているのは「0x7fff54bad4f8」とかのアドレスではなくて、「3, 5」という値が入ってる。

じゃあmain関数から投げたアドレスはどこ行ったの? と言うと、それは「apl, lmn」に入ってる。

それがわかるのは以下の行で、

    printf("リンゴ: %p\n", apl);
    printf("レモン: %p\n", lmn);

実行するとこのように出ている。

リンゴ: 0x7fff54bad4f8
レモン: 0x7fff54bad4f4

%pがポインタ型の変数(ここではapl, lmn)のアドレスを示してくれる。

Perlとの比較

ここまでのことを踏まえつつ、ぼくが思い浮かべるのはやはりPerlのリファレンスで、それと重ねながら考えてみると、ぼくはPerlのリファレンスっていうのは、複数のファイルをひとつのzipに圧縮するとか、あるいは海外旅行へ行くときの膨大な荷物を旅行カバンにパッキングすることなんかに似ていると思っている。

たとえば、以下のような配列があったとして、

my @fruits = qw/apple orange grape lemon/;

これを目的のサブルーチンへ、参照渡しで(値のコピーではなく値の現物ごと)渡そうと思ったら、配列のままでは渡せないので、一旦リファレンスにしてから渡す。

fruits.pl
#!/usr/bin/env perl
use strict;
use warnings;
use feature 'say';

my @fruits = qw/apple orange grape lemon/;

say $fruits[0]; #=> apple

foo(\@fruits);

sub foo {
    my $bar = shift;
    $bar->[0] = 'I ate an apple!';
}

say $fruits[0]; #=> I ate an apple!

この中の

foo(\@fruits);

というのが、ぼくのイメージで言うと、フルーツの詰め合わせを真空パックして外からは触れられないようにみっちり梱包して宅配に出す、みたいな感覚。あるいは上記の旅行カバンに詰めるとか、zipに固める、というイメージ。

で、サブルーチン foo の

    my $bar = shift;

でその梱包された荷物を受け取って、その中のリンゴを食べたっていうのが上のコードの話です。

じゃあ、同じことをCのポインタでやろうとしたらどうなるか、と想像してみると、ん〜、こんな感じか?

fruits_eat.c
#include <stdio.h>

void eat(int *apl, int *lmn)
{
    *apl -= 2;
    *lmn -= 4;
}

int main(void)
{
    int apple = 3;
    int lemon = 8;

    eat(&apple, &lemon);

    printf("リンゴ: %d\n", apple);
    printf("レモン: %d\n", lemon);

    return 0;
}

実行。

リンゴ: 1
レモン: 4

ふむ。最初にmain関数からリンゴを3個、レモンを8個送る。で、それを受け取ったeat関数のほうでそれぞれ2個と4個食べて、残りがリンゴ1個とレモン4個になりました、というコードですが。

こうして考えると、送付元でやっていることは、Perlならリファレンス化、Cなら「&」を付けてアドレス化。
そして受け取り側でやることは、Perlなら「->」とか「@$foo」とか「%$bar」とかでデリファレンスして操作することだけど、Cなら「*」を付けて操作する、ということかな。

なるほど。ぼくはCの場合、「*」を付けることで「アドレス」を「値」に戻すのかと思っていたけど、「*」は値そのものに戻す(コピーする)のではなく、デリファレンスした状態で中身をいじる、といったことだと思えば腑に落ちやすいかもしれない。
言い換えると、あくまでアドレスを経由した操作である、というような。

ちなみに、今参考にしている以下の本によれば、

C言語体当たり学習 徹底入門 (標準プログラマーズライブラリ)

C言語体当たり学習 徹底入門 (標準プログラマーズライブラリ)

「&」は「アドレス演算子」、「*」は「間接参照演算子」と言うらしく、なるほどここまで考えてみると、確かにそんな感じかなという気もしてくる。

まあ上記のように、Perlでは送付元から送ったリファレンスを、受け取り側でもリファレンスとして受け取るのに対して、Cでは送付元からアドレスを送って、しかし受け取り側ではそれを(いわばデリファレンスした後の)ポインタ型の変数で受け取るわけで、その若干ながら確かなズレがちょっと、まだ結構気にかかってはいますが。

ということで、大変煩雑な感じの内容になりましたが、自分的にはそれなりの理解を得られたかなという気もします。

今後の予告としては、構造体、共用体、malloc() などの写経にそろそろ入ってくるので、それらを理解することが楽しみです。

現時点ではまだ、上のサンプルコードとか、あるいはポインタ界のお約束コードことswap関数ぐらいしか書けないので、もう少しCならではのコードで出来ることを増やしていきたいところです。

C言語の航海日誌(3)

少し日が空きましたが、写経は少しずつ進めていて、前回から今日までに8本のサンプルコードを写経しました。

ページで言うとp101〜157まで。
全340ページなので、もうすぐ半分に辿りつきそうです。

写したコードは、たとえば以下。

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    FILE *in_fp;
    FILE *out_fp;
    int ch;

    /* 入力用のファイルポインタの取得 */
    in_fp = fopen("data.txt", "r");
    if (in_fp == NULL) {
        fprintf(stderr, "ファイルが開けません\n");
        exit(1);
    }

    /* 出力用のファイルポインタの取得 */
    out_fp = fopen("out", "w");
    if (out_fp == NULL) {
        fprintf(stderr, "ファイルが開けません\n");
        exit(1);
    }

    while ((ch = getc(in_fp)) != EOF) {
        putc(ch, out_fp);
    }

    /* 使い終わったファイルポインタを閉じる */
    fclose(in_fp);
    fclose(out_fp);

    return 0;
}

ポインタへの変数が出てきていますが、じつはまだこの段階ではポインタの解説は始まっていなくて、書中ではひとまず「おまじない」として扱うよう言われます。
実際にポインタの解説が始まるのはさらに40ページ以上先に行ってから。

このサンプルコードにおけるメイン・トピックは、fopen関数(ファイルのオープン)です。

ぼくは大体、Cのサンプルコードを写しながらいつも「Perlだとどうなんだっけ」と頭の中で比べているんですが、そういう比較対象というか参照軸がひとつあると、また印象に残りやすい気がしています。

で、CとPerlだとほとんど同じように考えられるものもあれば、ちょっと違うな……みたいなものもあって、このファイル入出力の関数は後者かなと。似ているところもあるけど、違って感じられる要素のほうが多いというか。
ちなみに、ほとんど同様に使えるなと思ったのはif文、while文あたりです。

もう一つ、これも印象的なトピックでした。

#include <stdio.h>

int main(int argc, char **argv)
{
    int i;

    for (i = 0; i < argc; i++) {
        printf("argv[%d]: %s\n", i, argv[i]);
    }

    return 0;
}

ポインタが2重にくっついていて、それについては上記同様、ここでも「おまじない」扱いですが、コマンドライン引数の受け取り方を少し把握できた気がします。

この後は、仮引数の使い方、関数の分割、プロトタイプ宣言などを学んで、その後にようやくポインタが出てくるようです。

ポインタについてはすでに同書を含む書籍の通読や、ドットインストールを通して概要はチェック済みですが、実際に手を動かしながら理解していくのはこの後なので、楽しみです。

C言語体当たり学習 徹底入門 (標準プログラマーズライブラリ)

C言語体当たり学習 徹底入門 (標準プログラマーズライブラリ)