Perlの正規表現における「先読み/後読み」に関する私的まとめ

はじめに

Perlの基礎文法をある程度身につけて、本職のプログラマーほどではないにせよ、趣味や自分の本業を助ける程度のことができるようになってくると、そのもう一段先、ぐらいのことを知りたくなってくる。

現在のぼくにとってそれは正規表現の「先読み/後(アト)読み」を習得することで、実際にはそんなに使わない可能性も高いものの、いつまでもぼんやり把握したままなのが気持ち悪いので、ここ数ヶ月で理解したところを自分なりにまとめておきたい。

普通の正規表現

まずは土台となる、シンプルな正規表現の例を作っておく。

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

my $fruits = 'applebananaorange';

if ($fruits =~ /banana/) {  #<= ココが正規表現
    say 'match!';
}
else {
    say 'not match.';
}

変数 $fruits の値には「banana」が含まれているから、これは「match!」を出力する。

先読み

それではさっそく、「先読み」を試してみよう。
(以後のサンプルコードではプラグマは書かない)

my $fruits = 'applebananaorange';

if ($fruits =~ /banana(?=orange)/) {  #<= 書き換え
    say 'match!';
}
else {
    say 'not match.';
}

このように書くと、まず「banana」にマッチした上で、その右側に「orange」もあれば真になる。
よって、ここでも「match!」が出力される。

もしこのようにすると、

my $fruits = 'applebananaorange';

if ($fruits =~ /banana(?=lemon)/) { #<= orange を lemon に変える
    say 'match!';
}
else {
    say 'not match.';
}

「not match.」が出力される。

後読み(戻り読み)

次に、「後読み」を試してみる。
これは「戻り読み」と言う人もいるらしい。

まずコード例。このようにすると……

my $fruits = 'applebananaorange';

if ($fruits =~ /(?<=apple)banana/) {  #<= ココ
    say 'match!';
}
else {
    say 'not match.';
}

「match!」が出力される。

ここでやっているのは、もし「banana」がマッチしていたら、その左側をチェックして、そこに「apple」があれば真を返す、ということらしい。

だから、以下のようになっていると、

my $fruits = 'applebananaorange';

if ($fruits =~ /(?<=strawberry)banana/) { #<= apple を strawberry に変える
    say 'match!';
}
else {
    say 'not match.';
}

「not match.」が出力される。

「先」と「後」がもたらす混乱

ということで、「先読み」「後読み」がやっていることというのは、けっこうシンプルであるように思える。

では何が問題なのかと言うと、「先読み」「後読み」という名称における「先」とか「後」とかいう表現が、時間の前後を表しているのか、それとも場所(位置)の前後を表しているのか、あるいはどっちにしても右のことなのか、左のことなのか、解釈の余地が多すぎて直感的にわかりづらい、ということがよく問題になっている。

たとえば、「後読み」の「後(アト)」という言葉は「金なら後で払うからさ〜」と言うように、「今より遅い時間」のことを示すこともあれば、「後に並んでください」と言うように、場所としての「後ろ」を示すこともある。

もし場所としての「後ろ」を示すのであれば、それに対応する反対語は「先頭」になるが、ではコードにおける「先頭」とは、果たして右側のことだろうか? それとも左側のことだろうか?

ぼくだったら、任意の1行のコードを指して、「その先頭はどこですか?」と聞かれたら、一番左を「先頭」としてイメージするだろう。

以下の例で言ったら、

applebananaorange

左端の「a」が先頭であり、「e」が最後尾である。
(これは「行頭」「行末」という表現にも対応する)

しかし実際には、上記の正規表現における「先」とは右側「e」の方を指しており、「後」は左側「a」の方を指している。

なるほど、混乱している。

結論は「進行方向」における先と後

こうした事態に対し、「日本語で考えるからいけないのだ。原語(英語)で考えればよいのだ」と主張したのが以下の記事で、書かれた頃には少し話題になった。

qiita.com

じつはこの本文にはちょっと勘違いがあって(記事冒頭でその旨の追記もある)、だから本文だけを読むとかえって混乱が増すのだが、論旨としては、

  • 英語なら「先読み」は lookahead、「後読み」は lookbehind と表現されており、誤解の余地が少ない。
  • だからみんなも「先」とか「後」とか言わないで、英語で表現しようよ。

みたいなことを言っている。(と思う)

しかし、この記事でより注目すべきはコメント欄の方で、そこでは、

  • 「先(前)」であれ「後」であれ、「進行方向」を基準に考えなければ混乱は避けられない。

みたいなことが言われている。

ここで言う「進行方向」というのは、文字が伸びていく方向であり、再びこの例で言うと、

applebananaorange

最右の「e」が先頭で、最左の「a」が最後尾を指す。

よって、これらを踏まえて言えば、

  • 「先読み」とは対象とする語句(上のサンプルコードだと「banana」)から見て、文の進行方向における前(右側)を読むことであり、
  • 「後読み」とは対象語句から見て、文の進行方向における後ろ(左側)を読むことである。

と説明することができるだろう。

なお、そのリンク先の記事でもやや説明が曖昧になっているようなのだけど、これを単に「時間の観点ではなく位置関係の問題として捉えればいい」と言ってしまうと、新たな(というか未消化の)問題が生じてしまう。

なぜなら、「時間の前後ではなく、位置関係の前後なのである」と言ったところで、たとえば人が待ち行列に並ぶときの「後ろ」とは、列がどんどん伸びていく方向を指しているが、マラソンランナーがフルマラソンを走るときの「後ろ」は、ランナーがぐんぐん進んでいく方向とは逆側(スタート地点側)を指している。

だから、「時間の前後ではなく位置関係の前後なのだ」という説明ではまだ足りない。

よって、その点も考慮しつつ説明するなら、

  • 「後読み」の「後」というのは、『時間』に関する「後でやっておくよ」の「後(アト)」ではなく、
  • また『位置関係』を示す場合の「後ろ(ウシろ)」とも言い切れず、
  • 『進行方向』における「後(ウシろ)」である。

などと言う必要があるだろう。

また、その辺と繋がるイメージで、同記事に関するこちらのブックマークコメントもわかりやすかった。

[コラム] 正規表現の先読み/後読みは、どう考えても名前が悪いので、呼称禁止令を出してルックと気軽に呼んでみませんか。 - Qiita

「チラ見」と「後方確認」を推したい。

2017/06/04 23:40
b.hatena.ne.jp
(「チラ見」は先読み、「後方確認」は後読みを指すのだろう)

ということで、本題は以上だが、せっかくなのでこの機会に、上記に関連する正規表現をいくつかまとめておきたい。

否定先読み

まずは、「先読み」かつ「否定」するやつ。

これはつい最近までほとんど使っていなかったが、何かの機会に一度使ったら、けっこう便利だと感じた。

my $fruits = 'applebananaorange';

if ($fruits =~ /banana(?!lemon)/) {
    say 'match!';
}
else {
    say 'not match.';
}

この場合、「banana」の後に「lemon」が無ければ真なので、「match!」が出てくる。

否定後読み

上とほとんど同じやつ。

my $fruits = 'applebananaorange';

if ($fruits =~ /(?<!strawberry)banana/) {
    say 'match!';
}
else {
    say 'not match.';
}

これも「banana」の左に「strawberry」がなければ真なので、「match!」が出てくる。

幅を持つもの/持たないもの

さて、ここまでに挙がっていない話題として、上に挙げたパターン群には「幅を持たない」という特徴がある。

「幅」というのは、手元のPerl入門同人誌『雅なPerl入門 第3版』によると、

マッチした文字が消費する文字幅のことさ。. っていうのは、1文字だから1幅。\Aなどは、マッチしても幅は持たないから、0幅なんだ。

とのこと。
(同書は会話形式で進んでいくので、こういう話しぶりになっている)

この特徴は、以下のような例で示すことができる。

まず、通常の正規表現ならば、このような置き換えが可能だが、

my $fruits = 'applebananaorange';

$fruits =~ s/applebanana/xyz/;  #<= applebanana を xyz に置換

say $fruits; #=> xyzorange

たとえば「先読み」を使って以下のようにすると、

my $fruits = 'applebananaorange';

$fruits =~ s/apple(?=banana)/xyz/;

say $fruits; #=> xyzbananaorange

というふうに、括弧内の「banana」は置換されない。

これは上の『雅なPerl入門』からの引用にもあるように、「先読み」の記法が「\A」、つまり「アンカー」と同様に機能することを意味している。

この「アンカー」という観点から、正規表現の先読み・後読みを解説した記事としてはこちらがわかりやすかった。
abicky.net

冒頭部分だけ引用すると、こんな感じで説明されている。

この内容を理解するためには「先読み・後読みはアンカー」という考え方が必要になってきます.
アンカーとは文字列内の特定の位置を表す物であり,文字列の先頭を表す ^ や末尾を表す $ がそれにあたります.普通の正規表現では文字に対してマッチしますが,アンカーは位置に対してマッチします.

また、この特徴は「後読み」や「否定先読み」「否定後読み」にも共通する。

# 後読み
my $fruits = 'applebananaorange';

$fruits =~ s/(?<=apple)banana/xyz/;
say $fruits; #=> applexyzorange
# 否定先読み
my $fruits = 'applebananaorange';

$fruits =~ s/apple(?!lemon)/xyz/;
say $fruits; #=> xyzbananaorange
# 否定後読み
my $fruits = 'applebananaorange';

$fruits =~ s/(?<!lemon)banana/xyz/;
say $fruits; #=> applexyzorange

(?:PATTERN) との違い

以上、「幅」についても一貫した法則があるようで、煩雑ではあるものの難しくはない、という感じだが、その法則を外れているのが、ここで新たに登場する「(?:PATTERN)」という書き方である。
(「PATTERN」は説明のために便宜的に使用する仮の文字列)

この記法は上に挙げた「先読み」とか「後読み」のように使うわけではなく、通常の括弧でくくった場合とほとんど同じ働きをする。

しかしもちろん、通常の括弧とまったく同じ働きならば、そもそも存在する理由もないわけで、じゃあどこが微妙に違うのかと言ったら、通常の括弧はくくった文字列をキャプチャ(捕獲)するのだが、この記法ではキャプチャをしない。

「キャプチャ」とは、その括弧でくくった部分を後から再利用するために一時記憶することで、たとえば以下のように使う。

my $fruits = 'applebananaorange';

# 括弧でくくった apple が $1 に入る
if ($fruits =~ /(apple)/) {

    # $1を使った文が出力される
    say "I like an $1!"; #=> I like an apple!

}

一方、ここで「(?:PATTERN)」を使うと、

my $fruits = 'applebananaorange';

if ($fruits =~ /(?:apple)/) { #<= 変更
    say "I like an $1!";
}

以下のように、「$1」には何も入ってないよ! と怒られる。

Use of uninitialized value $1 in concatenation (.) or string at (略)
I like an !

ふむ、ふむ。
……えーと、でも、それがなんなのだ?! 一体これにどんなメリットがあるのだ? という感じだが、前掲の『雅なPerl入門』ではまさにその疑問と、回答が示されている。

どういう時に使うんですか? 普通の( )でいい気がします。
 
メモリ消費を減らす目的かな。マッチする文字が大きいとメモリ消費も大きくなるし。

なるほど……。(わかったような、わからんような)

さてしかし、じつはこの「キャプチャしない」という性質自体は、上記の「先読み」「後読み」にも共通している。
だから、問題はそのことではなく、先述のとおり、

「幅」についても一貫した法則があるようで、煩雑ではあるものの難しくはない、という感じだが、その法則を外れているのが「(?:PATTERN)」という記法である。

この「(?:PATTERN)」が幅を「持つ」ということが問題なのである。

見た目や、他の挙動はほとんど同じなのに、そこだけが違う。混乱する。

たとえば、「先読み」を使った置換コードはこのように動くが、

my $fruits = 'applebananaorange';

$fruits =~ s/apple(?=banana)/xyz/;

say $fruits; #=> xyzbananaorange

同じことを「(?:PATTERN)」でやると、こうなる。

my $fruits = 'applebananaorange';

$fruits =~ s/apple(?:banana)/xyz/;

say $fruits; #=> xyzorange

出力結果を並べると、前者は「xyzbananaorange」だが、後者は「xyzorange」であり、前者では括弧でくくった「banana」がそのまま残っているが、後者では「banana」も「apple」と一緒に「xyz」に置き換えられている。

言い換えると、前者(先読み)は、括弧内が幅を持たない(=アンカーである)から置き換えられないが、後者は幅を持つ(=アンカーではない)から置き換えられてしまう、ということのようである。

うーむ、まぎらわしい……。
まあ、それで困る場面がとくにない、ということなのかもしれないが。

幅・キャプチャに関するまとめ

ということで、ここまでに触れてきた「幅」「キャプチャ」について、サンプルコードを列記して簡単にまとめておこう。

my $text = "abc";

# 幅の研究
$text =~ s/a(?:b)/x/; #=> xc
$text =~ s/a(?=b)/x/; #=> xbc
$text =~ s/a(?!z)/x/; #=> xbc
$text =~ s/(?<=b)c/x/; #=> abx
$text =~ s/(?<!z)c/x/; #=> abx

# キャプチャの研究
# 以下、すべてエラー($1に値が入らないため)
$text =~ s/a(?:b)/$1/;
$text =~ s/a(?=b)/$1/;
$text =~ s/a(?!z)/$1/;
$text =~ s/(?<=b)c/$1/;
$text =~ s/(?<!z)c/$1/;

まとめ 〜「前後」がもたらす深い謎〜

以上、Perl正規表現の「先読み」「後読み」、およびそれに関連する書き方を、今把握しているかぎり書き出し&整理してみた。

正直、ここまで書いてみても尚、これらをどんな状況で便利に使えるのか、ということはよくわからない。

少なくとも自分が書く程度の規模や内容であれば、ごく基本的な正規表現だけで足りそうではある。

しかし、とくに後半でまとめたような、キャプチャや幅に関する知見については、後から思い出そうとしてもなかなか思い出せないだろうから、記憶が鮮明なうちにこうしてまとめておくことには、一定の意味もあるだろう。

今回の記事作成にあたり、その「先読み」「後読み」ならではの使いどころというか、特徴や使い方を細かくまとめているブログ記事をいくつか見たので、合わせて記録しておく。

前者の記事では、なぜ通常の正規表現ではなく「先読み」を使うのか、みたいなことが少し書いてあって、参考になった。

それから、前半で話題にしたQiita記事のコメント欄からリンクされていた以下の記事。

考えはじめると頭が割れそうなほどに哲学的というか、面倒な難問ではあるが、すこぶる面白かった。

果たして、「前」とは、「後ろ」とは、何を指しているのだろうか?

「前を向いて生きろ! 後ろを振り返るな!」と言うときの「前」は未来であり、「後ろ」は過去を指しているように思えるが、「前にこんなヒドイことがあってさあ〜」と言うときの「前」は明らかに過去である。

ゾゾ〜……。(←わからなさに震えが走っているところ)

まあ、簡単に整理してしまえば、任意の出来事が10年前、5年前、去年、現在……という順に生まれ、1本の行列に並んできたと考えた場合、その一番「前」にあるのは最も古い過去だから、過去を「前」と呼ぶのは空間的な位置関係(たとえば商店や病院などの待ち行列)における「前後」のイメージと合致するが、そうした俯瞰的な見方ではなく、一人の人間が主体となって「俺はこれから前へ進んでいくぞ!」といった文脈における「前」は、未知の空間へ入っていくイメージを持っているから、時間の概念においても「過去」ではなく「未来」を指している、つまりそのように、主体の置き方によって「前後」の意味が変わるのだ、と言うことはできるかもしれないが。

しかしいずれにせよ、こうした言葉(というか訳語というか)を考えるというのは、なんとも重い責任を持つことであり、なかなか大変そうである。

とくに、この「先/前/後」のような両義的な言葉を使うのは、非常に高難度な所業であるように思える。

実際のコーディングにおいては、勉強や経験を積むほどに、「そんなのどっちでもいい。というか、どっちでも問題ない」みたいな感じになるのかもしれないが、学習の途上にある人や、まだ経験の少ない人にとっては、ちょっとした障害になることも確かだろう。

上にも書いたが、ひとまずはこれが未来の(前の?後の?)自分の役に立てば幸いである。

関連資料

Perl入学式でいつもお世話になっています @xtetsuji さんから、同件に関してご自身がどう理解しているか、手書きのメモを書いてシェアしてもらいました!
これ、むっちゃ面白い&わかりやすい。

上に書いたような基礎をある程度理解してからの方がよりわかりやすいかもしれないけど、とりあえず今まで見たどんな解説より「あ、そうなんだ」という感じになりました。

@xtetsuji さん、ありがとうございます!

バイリンガルニュースとGoogle音声入力による文字起こしでリスニング練習

最近、バイリンガルニュースというポッドキャストをよく聴いている。
bilingualnews.libsyn.com

ホスト/ホステスのマイケルとマミさんが、英語と日本語を混ぜこぜにして、世界の様々な分野の時事ネタを中心に話しているプログラム。

音声だけでも面白いけど、スマホMacのアプリから各回の文字起こしテキストを有料で読むことができて、それがすごく充実していて面白い。

文字起こしといえば、このブログでもけっこう馴染みのネタで、一応仕事としてもそういうことをやりがちなぼくとしては、つい同業者的視点からそれを見てしまうのだけど、英語と日本語が入り混じって90分ぐらい続く上に専門用語みたいなのもよく出てくる内容なのに、いつも音声エピソードの公開から2日ぐらい? で文字起こしが公開されていて、ええ、これどうやってんだろ? 普通に耳で聴いて手で打ち込んでるのかなあ、すごすぎだろ、とか思っていた。

で、これもこのブログの馴染み的なネタとして、以前からGoogleの音声入力機能を使った文字起こしというのを時々やっているので、もしかしたらそれ(音声入力ベースの文字起こし)をやってるのかなあ、とか思いながら、自分でも試してみたのがこちら。

www.youtube.com

サンプル的に使わせて頂いたのは、現時点での最新回である #BN274。(2分程度)

上の動画は、Googleドキュメントのメニューバーから「ツール/音声入力」で操作しているところ。

自分では一切打ち込んでなくて、時々カーソルを動かしてマイクのボタンをクリックしたりしている。

音声の方はSoundflowerというMacアプリでMac自体に音を聴かせて、それだけだと人間(私)には何が流れているかわからないので、進捗をモニターするためにLadioCastというアプリも一緒に動かしてる。

詳しくはこの辺にまとめています。

で、やってみた感想としては、「そんなに早くは終わらなそう」(笑)。
(まあ、ぜんぶ自力でやるより体はラクだと思うけど)

というのも、そのGoogleドキュメントの音声入力を使う場合、動画にもあるように、英語を入力したければ英語設定に、日本語をテキスト化したかったら日本語設定に、そのつど切り替えないと読み込んでくれないので、その切り替え作業をやっていると、その間に喋ったことが漏れてしまう。

なので、もしこれを使うなら、最初に英語は英語だけでざーっと拾ってしまって、その後に日本語は日本語設定でざーっともう一周聴かせて、それぞれのテキストをマージする、という感じになりそうだと思った。

で、長くなったけどここまでが前置きで、以下が掲題の件。

そこまでやってふと思ったのは、そうやって「英語のトークは英語設定で拾う」というのをやっても尚、やっぱり同音異義語とか、喋るスピードに機械が追いつかないとかの理由でどうしても拾い間違いが出てくるので、逆にというか、その拾い間違えた英語の部分を「間違い探し」的に当てていく、というリスニングの練習ができそうだなと思った。

具体的には、

1. まず上記の手順でざっくりテキストを作って、
2. その「とりあえず起こせてるっぽい英文」を眺めながらポッドキャスト音声を聴いてみる。
3. すると、たぶん耳で聴いたのとはちょっと違う、拾い間違えたテキストが出てくるはずなので、そこを自分が聴き取った言葉にどんどん直していく。
4. 「これ以上はどこが違うかわからないな」というぐらいまで直したら、上記のアプリから「正解」の文字起こしテキストを見て「答え合わせ」をしてみる。
5. ぜんぶ合ってたら次に行く。

みたいな。

面白そう!

と思ったんだけど、実際にはいろいろセッティングしたり、とりあえず最初の問題文(拾い間違いを含んだ英文テキスト)を作るだけでもけっこう手間がかかりそうなので、まあ非現実的か……。 :sweat_smile:

おまけ

リスニング練習とは関係ないけど、上の流れでGoogle翻訳を使っていたら、ここでも音声入力が使えることに気づいて、ちょうどその自動文字起こしセットを動かしていたので、戯れに似たようなことをやってみた*1
www.youtube.com
終わり。

*1:使用したエピソードは最初の動画の続き。

carvo update

f:id:note103:20170705123353g:plain

  • Macだと音声も出るようになっているので、音声付き動画も作りました。
    • 6秒目ぐらいで急に音が出ます。

www.youtube.com

  • 以下、つれづれに。
  • 昨日も別のところに書いたのだけど、今回はメンテナンスの邪魔になりがちなファットな要素(=便利だと思っていたけど本質的な要素とはちょっとズレた機能)をザクザク外すのが主な変更。
  • テストもちょっとだけ入れつつ。
  • 未解決の問題として、どうもどこかの段階で、ファイルへの回答記録記入の動作に不具合が生じてしまった模様。
    • ゲームの途中で単語カードを切り替えた場合、切り替える前の記録が消えてしまう。
    • 正答/誤答数などを記入する `result.txt` の方は無事なようだけど、詳細に英単語などを記録する `log.txt` の方が上書きされてしまうみたい。
    • 以前は何回切り替えてもすべて保持していたのだけど……。
    • よって、ヘルプから記録に関する文言を一旦カットして、忘れないように自分でIssue登録しておいた。
  • 今後のTODOとしては、上記バグ修正のほか、残りのテストを充実させること、その流れでTravis CIとの連携を復活させたいところ。
    • CI連携に関しては、以前に少し頑張ってカバレッジ可視化サービスと繋げて表示したりもしていたのだけど、なにしろ拙い&多めのコードを全然カバーしきれず効率悪かったので一旦やめていた。
    • まだまだ自分の中でも整理できないまま動かしている部分が多々あるので、その辺を整理しつつテストも補って、というふうに進めていきたいところ。
  • 最後にREADMEを貼っておきます。

    
      続きを読む
    
  

bashのエラーにハマって直った

最近は作業記録をけっこう細かい粒度で付けているのでわかるのだけど、昨日普段どおりに付けた最後の記録が

2017/06/14 Wed 13:07:02 今から**やる

だったので(**の部分は業務内容なので割愛)、少なくともその時点までは正常だったのが、次の記録では

2017/06/14 Wed 14:12:29 Dropboxのファイル移動が怪しいかな

となっていて、その1時間ほどの間に何かが起きていた。

現象

なんの話かというと、その14時頃に遭遇していた問題というのがあって、ターミナルを開くと

-bash: /Users/note103/.git-completion.bash: line 52: syntax error near unexpected token `elif'
-bash: /Users/note103/.git-completion.bash: line 52: `	elif [ -d "$1/.git" ]; then'
-bash: /Users/note103/.git-prompt.sh: line 120: syntax error near unexpected token `;;'
-bash: /Users/note103/.git-prompt.sh: line 120: `			;;'
-bash: /Users/note103/.bash_profile: line 82: syntax error: unexpected end of file
-bash: __git_ps1: command not found

みたいなエラーがザバッと出てくる。

あれ、と思って.bashrcを読み込み直すと、

$ source ~/.bashrc
-bash: /Users/note103/.bashrc: line 12: syntax error near unexpected token `}'
-bash: /Users/note103/.bashrc: line 12: `}'
-bash: __git_ps1: command not found

あらら。慌てて今度は.bash_profileを読み込み直すと、こうなる。

$ source ~/.bash_profile
-bash: /Users/note103/.bash_profile: line 82: syntax error: unexpected end of file
-bash: __git_ps1: command not found

さらには、僕は普段から自分で作ったPerl製のコマンドラインツールをよく使っていて、冒頭の作業記録もそれを使って記入しているのだけど*1、そのツールを使って「なんかよくわからんことが起きている」というメモを書こうとしたらこんなエラーが出て、

Can't locate TinyCalcs.pm in @INC (you may need to install the TinyCalcs module) (@INC contains: /Library/Perl/5.18/darwin-thread-multi-2level /Library/Perl/5.18 /Network/Library/Perl/5.18/darwin-thread-multi-2level /Network/Library/Perl/5.18 /Library/Perl/Updates/5.18.2/darwin-thread-multi-2level /Library/Perl/Updates/5.18.2 /System/Library/Perl/5.18/darwin-thread-multi-2level /System/Library/Perl/5.18 /System/Library/Perl/Extras/5.18/darwin-thread-multi-2level /System/Library/Perl/Extras/5.18 .) at /Users/note103/path/to/memo.pl line 8.
BEGIN failed--compilation aborted at /Users/note103/path/to/memo.pl line 8.
-bash: __git_ps1: command not found

メモすら取れなくなってしまった。

ちなみに、ここに出てくるTinyCalcs.pmというのは、その自分で使っているメモツールでuseしている自作モジュールなので、これはそのモジュールが読み込めてないよ、というエラー。

さらに言うと、ここで注目すべきはPerlのバージョンで、上には5.18と出ているけど、普段ぼくはplenvというPerlのバージョン管理ツールでインストールした5.24を使っているので、なぜかそれが無視されてPerlのバージョンまでダウングレードしてしまっている。

という、なかなか味わったことのないカタストロフィーにこの段階でゾ〜〜……ッとして、軽くパニックに陥ってしまった。

検証

上のエラーに戻ると、まず

-bash: /Users/note103/.git-completion.bash: line 52: syntax error near unexpected token `elif'
-bash: /Users/note103/.git-prompt.sh: line 120: syntax error near unexpected token `;;'
-bash: /Users/note103/.bash_profile: line 82: syntax error: unexpected end of file

あたりは、当のコードを見てもとくにそれ自体に問題があるとは思えない。

ちなみに、その初めの2行で出てくるシェルスクリプトはどちらもGitを便利にする系のツールで、前者はGitコマンドをタブで補完するためのもので、後者はプロンプトにブランチ名を表示してくれるもの。

解説は以下あたりがわかりやすいか。
qiita.com

それから、どのエラーにも等しく顔を出してくるこれ、

-bash: __git_ps1: command not found

これは、.bash_profileに記述されている以下に対応している。

PS1='[\h] \W$(__git_ps1 ":%s")\$ '

ようは、上記のコマンド補完スクリプト( git-prompt.sh )を読み込めないのでそのコマンドもないよ、というメッセージ。

あとは.bashrcや.bash_profileに関する以下のエラーに関しても、

-bash: /Users/note103/.bashrc: line 12: syntax error near unexpected token `}'
-bash: /Users/note103/.bash_profile: line 82: syntax error: unexpected end of file

ここで示されている各該当部分はとくに問題なさそうというか、むしろその直前まで何も問題なかったのだから、これを真に受けていろいろいじってしまったらかえって二次災害に繋がってしまう。

よって、具体的に手を動かすこともできないまま、「一体なにが原因なんだろう……」と悩みはじめ、まあ経験的にはどう考えてもその直前に無意識にやったことが原因であることは間違いないのだけど、あまりにもいろんな作業を並行して進めていたせいで、原因になりそうな特定の作業をまったく思い出せない。

そしてその時点では想像できなかったのだけど、これにハマったまま結局夜になってしまった。*2

もちろんその間も、いろんなサイトを検索して見て回ったのだけど、

結果的には、今回の件に関係あるものはひとつもなかった。

一方、じつは最初の段階で、「前にも似たようなエラーでハマって解決したことがあったんだよな……なんだっけあれは……」と思っていて、少ししてからそれは.bashrcに仕込んでいたエイリアスに「done」というエイリアス名を使っていたことが原因のエラーだったことを思い出したのだけど、とはいえその後は教訓を活かして「done」というエイリアス名は使っていなかったので、「ん〜、似てると思ったけどそれはナイか……」と却下して、いよいよ出口がなくなった。

こういう場合、プログラミングを職業としている人ならいつまでも個人的にハマっているわけにはいかないから、職場の先輩や同僚などにヘルプを求めると思うのだけど、自分はそういう環境もないし、Perl入学式の先輩たちに意見を聞くことも可能ではあったのだけど、再現条件を示すのも大変そうだったので、そのままさらに独自対応の道を進むことに。

で、とにかくもういろんな「あの手この手」の試行錯誤をして、何か必要な不可視ファイルを捨ててしまったのではないかとか(冒頭のメモに書いた「Dropbox」はその流れで疑った)、いっそMacのTime Machine機能を使って半日ほど前の状態に復元してしまうべきか、とかも考えたのだけど、そんな中でもとりあえず、Perlのエラーについてはたぶん.bash_profileに記載している以下の設定、

if [ -d ${HOME}/.plenv  ] ; then
    export PATH=${HOME}/.plenv/bin:${HOME}/.plenv/shims:${PATH}
    eval "$(plenv init -)"
fi

このplenvの設定を読み込んでいないことによるものだろうとは思っていたし(5.18というのもダウングレードというよりシステムPerlに戻った状態なのだろうと)、そうであるならひとまずはbashまわりのミスに狙いを絞っていいはずだと自分に言い聞かせて、気持ちを落ち着かせるようにした。

しかしそれ以上のことはまったくわからないので、とりあえず「もうどんな手を使ってもいいから一旦このエラーを全部消してみよう」と思って、まずはどのファイルのどの記述がエラーを出しているのか確認するために、.bash_profileと.bashrcに

echo 1

から

echo 5

までを適当に散らばらせてみた。

で、その状態で新たにターミナルを開いたら、こんな感じになった。

1
3
4
5
-bash: /Users/note103/.git-completion.bash: line 52: syntax error near unexpected token `elif'
-bash: /Users/note103/.git-completion.bash: line 52: `	elif [ -d "$1/.git" ]; then'
-bash: /Users/note103/.git-prompt.sh: line 120: syntax error near unexpected token `;;'
-bash: /Users/note103/.git-prompt.sh: line 120: `			;;'
-bash: /Users/note103/.bash_profile: line 84: syntax error: unexpected end of file
-bash: __git_ps1: command not found

おっと、面白い。出力された数字から「2」が抜けている。

具体的には、このとき1と2は.bash_profileのトップと最終行に入れていて、3,4,5はそれぞれ.bashrcのトップ、真ん中、最終行に入れている。
(.bash_profileは80行程度であるのに対して、.bashrcは500行を超えているのでそのように配分した)

で、なぜ出力がこうなるのか考えてみると、.bash_profileの上の方には以下の記述があるので、

if [ -f ~/.bashrc ] ; then
    source ~/.bashrc
fi

おそらくコンピュータは最初に.bash_profileの1を出力して、その後に上記の呼び出しを伝って.bashrcの3,4,5を吐き出して、また.bash_profileに戻ってきたところでエラーを吐いて、5に至る前に死んでいる。

では、という感じで、今度はその呼び出しをコメントアウトして、

# if [ -f ~/.bashrc ] ; then
#     source ~/.bashrc
# fi

その状態で新たにターミナルを開いてみると……

1
2

おお。エラーが消えた。そしてさっきは出てこなかった「2」(.bash_profileの最終行)がちゃんと出てる。

ということは、問題は.bash_profileの方にはない。
加えて、マシン自体やその他のアプリケーションとかからの影響で不具合が起きてるわけでもない。

これはやはり、.bashrcで何かよからぬものを拾って、その状態で.bash_profileに戻ってきたから発生したエラーなのだ。

……という仮説に基づいて、ここからは.bashrcの検証へ。

追跡

まずは先ほどの.bash_profileのコメントアウトを外して、さっきまでと同様のエラーがすべて出る状態に戻す。

1
3
4
5
-bash: /Users/note103/.git-completion.bash: line 52: syntax error near unexpected token `elif'
-bash: /Users/note103/.git-completion.bash: line 52: `	elif [ -d "$1/.git" ]; then'
-bash: /Users/note103/.git-prompt.sh: line 120: syntax error near unexpected token `;;'
-bash: /Users/note103/.git-prompt.sh: line 120: `			;;'
-bash: /Users/note103/.bash_profile: line 84: syntax error: unexpected end of file
-bash: __git_ps1: command not found

次に、500行以上ある.bashrcのファイルのうち、1行目に置いている

echo 3

から、真ん中あたりに仕込んでいる

echo 4

までを残して、そこから最終行までの下半分を一旦カットしてターミナルを開いてみる。

1
3
4
2

ヤッホー! エラーが消えた。そして残したecho文の数字も全部出ている。

ということは、.bashrcの真ん中に設置した

echo 4

の次の行から、先ほどカットした最終行の

echo 5

までの間に、問題の「それ」はあると考えられる。

ここまで来ると、もう先ほどまでの苦悩やヤケ感は何だったのかと思うほど体がラクになっている。

あとは単純に、これまでの繰り返し。機械的に対象領域をどんどん半分に区切っていって、最後までエラーを吐いている行を追い詰めればいい。

で、追い詰めた犯人はこれ。

alias fi="perl /path/to/findline.pl"

(パス名は仮のもの)

ここでようやく、「あ〜……なんだよやっぱり、前に経験したやつと同じじゃん!」と気がついた。

つまり、さっき上の方で「それはナイ」と却下した「done」と同じ。

簡単に解説すると、そもそも「done」をエイリアス名に使って何が問題だったのかというと、おそらくシェルスクリプトのfor文でそれを使うからだろう。

こんな感じのやつ。

for i in foo bar baz
do
    echo $i
done #<=ココ

で、この「fi」というのもまさにそれで、「fi」はシェルスクリプトのif文でこのように使う。

s=foo
if [ $s == foo ] ; then
    echo FOO!
else
    echo BAR!
fi #<=ココ

(この場合は「FOO!」と出力される)

つまり、今回も「done」のときと同様に、構文に出てくる「fi」をエイリアス名に使ってしまったから各種のエラーが生じたのだと思われる。

実際、とりあえずその「fi」を別のものにしたらエラーはすべて消えたし、それによってPerlも元通り5.24.0に戻ったし、そのPerlを使った自分のモジュール&コード群もすぐ使えるようになった。

この時の記録。

2017/06/14 Wed 19:40:55 Dropbox全然関係なかったじゃん

まとめ

*1:いずれここでも紹介したいが。

*2:一応業務もやっていたけど、解決しないまま夜になるとは思っていなかった。

変数は「箱」か?(3)

概要

  • 本記事では、「プログラミング入門者に初めて《変数》を教えるとき、どのように説明することが適切か」ということ、そして「その際に《箱》を喩えに用いることで生じる問題を、別のどのような説明を用いることで解消できるか」ということについて考える。
  • この際、まず何よりも重要なことは、「議論の前提を明らかにし、それを一致させてから議論をスタートする」ということである。
  • 他の場面で他の人が似たような話題をとり上げているかもしれないが、本記事と同じ前提や問題意識による議論であるかはわからない。似たような話題だからといって、その目的や前提も安易に同一視してしまうと、関わる皆の時間が無駄になってしまうので、注意が必要である。
  • 箱以外の説明の仕方には、さしあたって良いものが二つある。
    • 変数を「名前を付けたデータ」であると説明する
    • 変数を「あだ名」に喩える
  • ただし、変数の利便性を伝える最も効率的な方法は、それを使った場合と使わなかった場合とのコード例を比べて見せることである。
  • このような問題提起に対しては、「しょせん比喩に過ぎないのだから、その疑問自体がおかしい」という定番の応答がよくあるが、それはメタレベルからの視点にもとづいており、疑問の真意を直視していない。
    • 「比喩」が「現実ではないこと」を理解できない人などいないし、稀にそのような人がいるとしても、このような疑問を呈したからという理由でそう決めつけるのは非論理的である。
  • 本記事は、「箱を使った喩えが悪い」という主張でも、「そのように喩えた人が悪い」という主張でもない。より良い変数の説明の仕方を考えることを目的としている。
  • 論理を丁寧に積み上げていくと、以前に採用していた粗雑な論理には戻れなくなる。そして、箱の喩えに疑問を感じた人に対する「比喩と現実を並べて考えてはいけない」という意見は、その雑な論理から導かれている。
  • そのような粗い論理の段階を早々に抜け出す一助になればと思い、これを書いた。

はじめに

この話については、すでに2度も長文を書いていて、

変数は「箱」か? - the code to rock
変数は「箱」か?(2) - the code to rock

かつ、今それらをざっと見返してみたら、今回書こうと思ったこともほぼ書いてあったので、じゃあそれでいいか、という気もしたのだけど、話題自体はまだ時々見かけるというか、以前にまとめたそれらの考えがあまりちゃんと外に届いていないようなのと、あとは前に書いたものの方は自分でもまどろっこしいというか、その時点でも「あまり上手く書けないなあ」と思いながら書いていて、しかしそれからしばらく時間が経って、もう少しシンプルに「こんな感じかなあ」というふうに思えるようになってきたので、今の視点からあらためて書き直してみたい。

前提を定義する

変数を箱に喩えるのがいいのか悪いのか、あるいは「良くも悪くもないけど何らかの問題がある」のか、それとも「問題なんてない」のか、みたいな議論が起こるとき、どうもありがちなパターンとして、異なる見解をもつ人同士が「同じ前提」を共有していない、ということがあるように思う。

前提や目的が一致していないと、途中で「そもそも何が問題なんだっけ?」みたいになりやすい。

そうなると、時間の無駄とまでは言わないまでも、やはり不毛感や消耗感から逃れがたい。
誰もそんなものは望んでいないのだから、まずは「前提」を一致させ、共有しなければいけない。

では、この記事における「前提」は何かというと、変数を「箱」に喩えることに対する、ぼくが持っている次の意見に集約される。

 プログラミングを学ぶ初心者に対して、「変数」の使い方を初めて教えるとき、変数を箱に喩えて、「値を箱に入れるイメージ」を伝える人がいるけれど、現実の世界で箱に物体を入れてしまったら、その物体を複数の場所から同時に取り出すことはできないのだから、本来そのようなことが可能になる変数の有用さを、箱のイメージでは伝えることができない。
 たしかにその喩えは、値を「格納」するイメージを伝えることには適しているが、「ひとつの値を複数の場所で同時に使える」という変数の性質を一緒に説明できる別のイメージがあった方が良いのではないか。

まあ、これだけと言えばこれだけである。

これに対しては、「いやいや、こういうときはひとまず値を《格納》するイメージだけでも伝えられれば良いのだよ。複数の場所でどうした、なんていうことはまた別に説明すればいいのだ」という意見もあると思うし、そういう意見があるのはまったく構わない。

重要なのは、どちらの意見が正しいのかということではなく、ましてやどちらか一方の意見に統一することでもなく、「何を目的にこの話を進めるのか」という、前提を一致させておくことである。

前提に関する行き違いを解消する

しかしながら、上記の前提を受け入れられない(同意できない)という人もいると思う。

というより、冒頭に書いたこととも繋がるが、この話で行き違いが生じるとすれば、その大半は、上記の前提を受け入れられるか、受け入れられないか、という違いに起因しているようにも思える。

上の話に対して「なるほど、たしかにそうだよね」と思えない人の中には、おそらく「しょせん比喩じゃないか」と思う人が少なくないだろうと想像する。

「しょせん比喩じゃないか」に続くのは、「比喩に過ぎないものと現実世界の物体とを繋げて考えるなんて、馬鹿げている。考えるだけ時間の無駄だ」といったものだろう。

そして、このズレこそが、もしかすると本件における最大の論点なのではないかと個人的には思っている。

「箱に喩えたら矛盾が生じるじゃないか」というぼくの疑問は、上の「前提」の最初に出てくる、「プログラミングを学ぶ初心者」の視点である。

一方で、「そんな疑問は馬鹿馬鹿しい」という回答は、プログラミングをすでに身につけた、「教える側」の視点によるものである。

この二つの視点は、同じ地平に立っていない。永遠に交わらない二つの線分である。

じつを言えば、最初にぼくが「箱」で説明を受けたとき(そう、「箱」で説明を受けたのだ)には、べつにそれを不思議とは思わなかった。
よく言われるように、その喩えを「わかりやすい」とすら思った。

しかしながら、自分で変数を使ってあれこれやれるようになってから、「あれ……でも、箱じゃ喩えとして変じゃないか?」と思うようになった。
どのような意味で「変」だと思ったのか、何が矛盾すると思ったのかということは、上に書いたとおりである。

これを聞いて、「箱でよい」と思っている人は「そら見たことか」と思うだろう。

「キミは箱の喩えを聞いて変数を理解したのだろう? そして自分で変数を使えるようになって、後から矛盾に気づいたのなら、それはキミが成長したということだよ。キミのその成長こそが、箱の喩えが学習に適している証拠なのだよ」と。

しかし、それは話がズレている。

ここでの議論の目的は、「初学者が最短コースで変数を理解すること」ではない。
「最終的に変数の性質をちゃんと理解できること」でもない。

ここで明らかにしたいのは、「箱に喩えることによって生じる矛盾を、何か別の説明の仕方で解消できないか?」ということだ。
「箱では変じゃないか?」というのはそういう疑問である。

その疑問に対して、「最終的に理解できるんだから問題ない」というのは答えになっていない。
視点が一段、メタレベルに上がってしまっている。

「最終的に理解できることがいいことかどうか」という問題なら、誰だって「理解できるのはいいことだ」と答えるだろう。

また、もしぼくが「箱に代わる説明の仕方さえわかれば、最終的に変数を理解できなくたっていい」と主張しているなら、それに対して「経緯はなんであろうと、最終的に理解できればいいんだよ」という反論も成り立つが、そういう話もしていない。

ここまで書けば、「比喩に過ぎない」とか「わかればいい」といったことはもう言う必要がなくなるのではないだろうか。

というかまあ、そもそも箱の喩えが「比喩に過ぎない」ことがわからない人などいるのだろうか? という疑問もあるのだけれど。

それが比喩に過ぎなくて、現実そのままの話ではないことなど当然わかっている。
それが比喩であるという前提で、「比喩として」もっと良いものがあるのではないか? と言っているのである。

このような質問をした人が、なぜ「比喩と現実の区別がつかない人」として扱われなければならないのか。そこにもまた、じつはこの議論を左右する核心が隠れているような気がしているのだけど、その問題については追々回収していきたい。

参照渡しは関係ない

さて、大前提についてはすでに説明したが、より具体的に話を進めるためには、もう少し細かい前提を補足していく必要がある。

よくこの話をしていると、「変数といっても、値渡しの変数と参照渡しの変数で性質が違うからなあ」みたいな意見を聞くことがある。

しかし、ここでぼくが想定しているのは、値渡しに使う変数でも、参照渡しに使う変数でも、どちらでもよい(というか関係ない)ものだ。

繰り返しになるが、ここで前提としているのは、「プログラミング初学者に初めて変数を教えるとき」のことである。

プログラミング初学者に初めて教えるのが「参照渡し」を主とするプログラミング言語でも、「値渡し」を主とする言語でも、その初学者にしてみれば大した違いではない。

それに、これも上に書いたように、ここで言う「箱の矛盾」というのは、「一回代入しただけで複数の場所で同時に使えるようになる」という変数の現象を「箱」では説明できない、ということである。

この点に関しては、値渡しであろうが参照渡しであろうが変わらないはずである。

つまり、これもまた「前提」の問題であって、今回ぼくはそのような前提を持っているが、そのことを知らずに「変数と言っても値渡しと参照渡しがあるから……」と考えはじめてしまったら、それは無用な回り道になる。

配列も関係ない

また同様に、「配列」も関係ない。

たしかに配列を説明するときには、ぼくも「箱」(というか下駄箱など)は有用な喩えとして使えるかもしれないと思う。

しかし、ここでの前提に配列は関係ない。プログラミングの初心者に配列の説明から入るケースはほとんど無いだろうし、そのような教え方があるとしても、ここではそれを前提としていない。

理想的な変数の教え方

ここからさらに、具体的な点を補足する。
ようやく少しだけコードも出てくる。

もしぼくが、初心者に変数の概念を教えるなら、たぶんこういう順番で教える。
見てもらえればわかるが、ここでは喩えを一切使わない。

まず、以下のような式を並べる。
Perlに慣れているのでPerlで)

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

say 13 + 1;
say 13 - 2;
say 13 * 3;
say 13 / 4;
say 13 % 5;

これを実行すると、こんな。

14
11
39
3.25
3

次に、このプログラム内の「13」をすべて「21」にしましょう、と言う。

Vimなどを使っていると一瞬でできるけど、ポチポチ書き換えているとけっこう面倒くさい。

ましてや、ここでは5行しかないけど、もし業務で100行も200行も同じことをする羽目になったら結構つらい。
ということを軽く言っておく。

ちなみに、「13」を「21」に変えて実行するとこうなる。

22
19
63
5.25
1

さてそして、ここで変数を紹介する。
まずは先ほど「13」から「21」に変えた場所に、今度は変数xを入れてもらう。
こんな感じで。

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

my $x = 21;

say $x + 1;
say $x - 2;
say $x * 3;
say $x / 4;
say $x % 5;

実行結果は前述のとおり。

そしてさらに、満を持してという感じで、「21」を「13」に戻してもらう。
もちろん、こうなる。

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

my $x = 13; #<= ここだけ変更

say $x + 1;
say $x - 2;
say $x * 3;
say $x / 4;
say $x % 5;

先ほどは5行分の修正が必要だったが、今度は1箇所変えただけで終わった。

そしてここでまた、「これがもし100行や200行あったらどうなったか」とか言ってみる。

変数というのは基本的に、こうやって「それがなかった場合に比べてどれだけ便利になるのか」を示しながら教えるのがいいと思う。*1

だから、そうではなく「言葉」で説明するとき、つまり箱などの喩えを使うときというのは、あくまでその本題的な説明(実際のコードを示しながらの説明)に入る前のイントロ程度のもの、と考えるのが合理的だと思える。

喩えを使わない説明 〜名前を付けたデータ〜

そこまでを踏まえた上で、ではその「言葉」で説明するときに、箱の喩えを使わずにどう説明したらいいのかといったら、以下の二つのあり方を示したい。

  1. 「名前を付けたデータ」と説明する
  2. 「あだ名」に喩える

まず前者だが、個人的には、ほとんどこれだけで充分という気もしている。

これの元ネタは、ござ先輩こと湯本堅隆さんによる以下の本である。

独習Python入門――1日でプログラミングに強くなる!

独習Python入門――1日でプログラミングに強くなる!

ぼくはいつも、入門書のたぐいを読むときには避けがたく「この本は変数をどう説明してるのかな」と、その本の試金石のようにそれをチェックしてしまうのだけど、この説明にはかなり関心してしまった。*2

非常にシンプルで、まあ身もふたもないとも言えるけど、いずれにせよ著者がちゃんと自分の体験と考えにもとづいて出してきた説明だと感じる。

そしてこの説明を見て、「べつに何かに喩えなくてもいいのでは?」という思いも少し強くなった。

喩えを使う説明 〜あだ名〜

と同時に、「何かもっと適した喩えはないのか」と考えてきた身としては、それはそれでちょっと悔しいというか、何か具体的な物事に喩えたい気持ちも落ち着かない。

そんな中で、そういう「喩え系」として現状一番いいかなと思うのが、上記後者の「あだ名」である。

まあニックネーム、あるいはTwitterなどのWebサービスのアカウント名も近い感じだろうか。

一瞬話がズレるが(と言っても変数の話だが)、以前に基本情報技術者試験の勉強をしていたら、本の中で「変数というのは中身の値がコロコロ変わっていくから《変数》と言うのだ」と説明していて、それもけっこう目からウロコだった。

だったら「変数」ではなく「変値」とかにしてくれたらもう少し直感的だったのに、とも思ったが、それはそれとして、「中身がコロコロ変わる」という性質は変数を語る上でたしかに重要である。

上に示したコード例でも、「13」が「21」に変わり、それがまた「13」に戻っていく。

そのまま話を戻すと、人のあだ名というのも、中身は交換可能である。

まあ「永ちゃん」と言えば矢沢永吉だし、「ミスター」と言えば長嶋茂雄だが(たぶん)、それは定数みたいなものだとして、たとえば「健ちゃん」とか「よっしー」とか「ジョニー」とかは、地域や時代によって、対象となる実在の人物がバラバラのはずである。

ここでまた一瞬離れてしまうが、ぼくがプログラミングに入門して、エンジニア界に近づいていろいろ受けたカルチャーショックの中でも、とくに印象的だったのは「みんなハンドルネームで呼びあってる」ということだった。
「本名は知らないけど、Twitterのアカウント名は知ってる」みたいなケースがけっこう多い。

これって何かに似ているなあ、とその時に思ったのが、昔はデイヴ平尾とかジョニー大倉みたいに外国人の名前をニックネームにして、「よう、ジョニー」とか言い合ってたんだよなあ、見たことないけど。なんかそれに似ているなあ、ということだった。

あらためて話を戻すと、その「あだ名」とか、あるいは「肩書き」、またあるいはその上位概念である「名前」などはじつに「変数的」である。

たとえば居酒屋で、そこに同席していない部長や社長についていろいろ話題にするとき、その場に部長や社長本人がいなくても、「このまえ部長がどうしたこうした」と言えば、皆の頭の中にその人の姿が思い浮かぶ。

これは変数が同時に複数の場所で呼び出され、それぞれが同じ値を取り出していることによく似ている。

そして「部長」や「社長」の中身が交換可能であったり、あるいは「よっしー」の中身が高校時代の友達である吉沢くんと会社の同僚である吉田さんとで交換可能であったりすることも、また変数的である。

ここでは、箱に見られた矛盾(破綻)が解消されている。

逆に、もし同じ状況を箱で再現しようとしたら、たとえば「部長」と書かれた箱の中に実在の部長が入らなくてはいけなくなる。
そしてそうなれば、複数の異なる場所で同時に部長の話をすることもできなくなる。

そう考えると、そもそも物体に喩えることに問題があるように思えてくるが、ある現象を何か他の事象に喩えるメリットというのは、複数の異なる人間が「ありありとした実感」を共有することにあるので、必ずしも物体に喩える必要はないし、物体以外のもの(つまりあだ名や肩書き)に喩えることでそれを実現できるならそれでいい。

「わかりやすさ」とは何か

ということで、ひとまず結論的なことも書いたので話は終わりに向かうが、まだこの文章で触れていなかったことについていくつか触れておきたい。

まず、これは少し前にTwitterにも書いたのだけど、「箱に喩えればわかりやすい」などと言うときの「わかりやすさ」とは、「その情景をイメージしやすい」という意味でのわかりやすさであって、「変数の性質を理解しやすい」ということとイコールではない。

思うに、「箱ならわかりやすい」と言う人はそこをごっちゃにしている。

「箱にモノを入れる様子」がイメージしやすいことについては、ぼくも同意できる。

しかし、それは「言葉から映像を想像しやすい」ということであって、「変数の性質を理解しやすい」ということとは別である。
まずはそこを分けて考えるべきである。

プログラマーは本当に箱を想像しているのかもしれない

と同時に、ぼくは最近、プログラマーは変数に値を代入しているとき、実際にモノを箱に格納しているかのようなイメージを頭の中に思い浮かべているのかもしれない、とも思っている。

というか、むしろプログラマーが普段からそういう状況を頭に思い浮かべているからこそ、変数を「箱」に喩える説明が生まれたのではないかと思っている。

ぼくが「箱に入れてしまったら、複数の場所から同時に取り出せないではないか」と言ったとき、「それとこれとは別の問題だ」という反応があるのは、現実空間にある箱と、プログラマーの頭の中にある箱とが、異なる性質を持っているからではないだろうか。

もしそうであるなら、教える側(プログラミングをすでに身につけた人)が、教わる側に「箱」と言ったとき、それは初めから「現実には存在しない不思議な箱」を意味している。

またそうであるなら、「現実世界に存在する箱と性質が違うよ!」なんて言ったところで、「何を自明なことを……」と思われてしまうのも理解はできる。

しかし一方で、このように「変数と箱」の問題がくすぶり続けるということは、その教える側の「自明さ」が、必ずしも教わる側にきちんと伝わっていない、ということなのではないだろうか。

そしてこの点を解消できないかぎりは、同様のコンフリクトがもうしばらく続いてしまいそうな気がしている。

それでも大切にすべきこと

さてその上で、もしそのように、認識のズレにある程度避けがたい経緯があるとしても、より優先すべきことは、「箱に喩えるのは変じゃないか?」といった疑問が挙がったときに、上に書いたような「メタレベル」の回答をしたり、相手の真剣な疑問を踏みつぶしたりしてしまわないことである。

よくTwitterでは、大人の矛盾した説明に対して素朴な疑問を提示した小学生が、教師から理不尽に叱責されるエピソードなどがRTで回ってきて、「こんな教師は嫌だ」みたいな話が盛り上がるけれど、そのわりにこういう疑問に対しては冷淡というか、情の薄い反応が少なくないのは不思議というか、むしろ人間の本性がそのようなものだから、そういう小学生のエピソードもなくならないということかな、という気もしてくる。

「人」を否定しているわけではない

その話とも繋がるが、この変数と箱の話題に対しては「なぜそんなことを言うのかわからない。まったく無意味である」という、攻撃のようにも受け取れる感情的な反応が少なくないと感じるのだけど、それはもしかすると、「箱」を例に使って教えたり、教わったりしてきた自分を否定されたように感じさせてしまうからかな、と思うこともある。

だから明記しておくのだけど、これはそういう例を使っている「人」を否定する話ではない。

結果的に、この話題によって自分を否定されたような気持ちになる人はいるかもしれないし、それを100%避けることもできないのだけど、目的はそこにはない。

目的は、「もっと良い説明はないだろうか? それを考えてみたい」ということである。

極められた思考がもたらす不可逆な論理

ここまで長く書いてきたのは、「ある種の思考の道筋は、一度通ったら戻れなくなる不可逆性を持っている」と考えているからだ。

どれだけ意見の異なる人同士でも、「それでも一致する共通見解」というものはあるはずで、それが見つかってしまうと、もうそれより前には戻れない。

少し前に、天動説と地動説の盛衰について語った面白い記事があったけど、

gendai.ismedia.jp

どれだけ多様な観点から考えたとしても、現代において天動説を本気で(地動説より)支持する人は出てきえない。

深い思考を丹念に積み重ねれば、「たしかにそこまでは言えるよね、そこまでは同意できるよ」という共通見解を作っていくことができる。

それは、「もう今から天動説には戻れないよね」というのと同じで、「ここより前には戻らない」という不可逆な地点の発見でもある。

「箱」で説明を受けた人が、「それだと矛盾しますよね?」と言ったときに、「ただの喩えだよ」と答えるのではあまりに雑すぎる。
というより、それはまともに答えられないことを誤魔化しているだけである。

「箱」で説明するのが悪いわけではない。上にも書いたが、説明する人の頭の中では実際に「値を箱に格納するイメージ」が動いているのかもしれないし、もしそうなら、それはもはや「喩え」ではなく「事実の伝達」である。

でも、その説明で納得できないという人がいるなら、「ただの喩え」などと回答するのではなく、一体どこに疑問を持っているのか、それを解消するにはどんな説明が必要なのか、一緒に考えてみてほしい。

そんな暇がないのであれば、非情な言葉を投げつけるのではなく、静かに離れてあげてほしい。

すでにここまでは考えた。ここより前の段階には、もう戻らないでほしいなあと思っている。

*1:変数に限らず、新しいことを教える際には多くの場面で有効な方法だと思う。

*2:厳密には「名前を付けられたデータ」と説明されているが、こちらでそのように書くと少し冗長になるので、「られ」は端折らせてもらった。

Qiitadonについて語る

f:id:note103:20170602192506p:plain

f:id:note103:20170602192525p:plain

というわけで、Qiitadonについて書いてみたいと思います。

Qiitadonは、今週の初めにオープンしたQiita/Incrementsによるマストドンインスタンス

qiitadon.com

公式アナウンスはこちら。

速報系としてはこの辺とか。

その他、マストドン全般についてはITmediaの以下の連載を見ていればほぼ100%追えるはず。
www.itmedia.co.jp

初期の盛り上がりみたいなものについてはぼくもこの辺に書きました。
scrapbox.io

もうひと月以上経ってるのかあ……。

さて、そのQiitadonですが、IT企業系だとぼくの知るかぎりpixivのPawooドワンゴfriends.nicoに続いて3つめの参入。

pixivもドワンゴ(というかニコ動)もそれぞれ核となる自社サービスおよびそのコミュニティみたいなものがあって、それと連動した感じで展開していることを思うと、たしかにQiitaもそういう面があるので、それでやることにしたのかなあ、とも思ったり。

一方、friends.nicoは先行するmstdn.jpにちょっと似て、ノンジャンルに近いインスタンスに見えるんだけど、Pawooは明らかに絵に関心が強い(描くのも見るのも)ユーザーが集まっていて、その意味ではQiitadonはすでに技術系のネタに興味のある人が集まっている感じがするので、Pawooに近いように思える。

さらに一方、Pawooの方はけっこうどしどし投稿が流れていくんだけど、Qiitadonの方はそうでもない。
これはたぶん、Pawooの方は半匿名的に参加できたり、自分の作品をどんどん投稿していくような文化があるのに比べて、Qiitadonの方はけっこうリアルな立場で参加している人が多そうというか、なんか変なことゆったときに自分がこうむる影響が少なくなくて、それで若干自重ぎみになるのかなという感じはする。

マサカリ回避傾向というか。

まあ、とはいえ個人的にはそれはそれ、ひとつの特色というか、インスタンスごとに持っている性格の一部として捉えればいいのではとは思っている。

その話にもちょっと近いけど、Qiitaにはすでにけっこう独特のコミュニティ感みたいなものがあって、個人的にはちょっとはてなに似ていると思ってる。

最近だとコミュニティ・ガイドラインを発表したら軽く賛否両論になったり。*1 *2

まあ、話題にのぼるというのはそれだけ関心が寄せられているということだから、それ自体はいいことだろうけど、どうも中の人の意向がうまく外部に届いてない気がするなあ、という印象は持っていた。

賛否両論の「否」の方がなんで出てくるのかっていうと、いろんな要因があるとは思うけど、けっこう大きいのは「中の人からの反応がナイから反応が来るまで強く叩いてしまう」という、人間が本来的に持っているある種の幼さというか、抱えたフラストレーションを我慢できずに発散してしまう、みたいなところがあるんじゃないかと個人的には思っている。

Qiitaはその辺に対しても真摯に取り組んでいるとは思うのだけど、そこでさらにこのQiitadonが機能することによって、その辺の「ユーザーと中の人との間を埋める潤滑油」みたいな感じになるのでは、とちょっと期待している。

もう少し具体的なことでも、いろいろ期待できることはある。

たとえばこれも「隙間を埋める」という感じだが、「ちょっとした技術系の気づきを得たけどQiitaに書くほどのボリュームでもない」とか、「Qiitaに書くにはちょっと技術要素が弱い(いわゆるポエム)」みたいな話について、さらっと投稿できる場所になれば良さそうだと思う。

案外、人は最初のきっかけというか、入り口さえ見つかってしまえば、だんだん書いているうちに「ちょろっと書くだけのつもりだったのにけっこうなボリュームになったな」みたいなことになりがちなので*3、Qiitadonが呼び水になってQiitaへの記事投稿につながるかもしれないし、自分では「大したことない」と思っていたネタが思いのほか多くの人に役立った、なんてことにもなるかもしれないし。

Qiita本体は上でもちょっと触れたように、ガイドラインが必要になるほど「どこからどこまでが《技術ネタ》なんだ?」みたいな範囲の問題とか、あるいは「こんなネタ誰も必要としてないよな……」みたいな基準的なことで投稿を躊躇してしまう部分もあるかもしれないけど、Qiitadonの方は、

技術に関する話題に限らず、気軽に投稿してください:)

とのことなので、いろんな意味で基準ゆるめで使ってみればいいのではないかと思う。

ぼくは今までmstdn.jp, Pawoo, friends.nico というインスタンスを使ってみたけど、やっぱりマストドンってローカルタイムラインがけっこう特徴的というか、それを見て楽しめないと単に「500字書けるTwitter」というだけみたいになりがちなので、現在ノンプログラマーとしてプログラミング入門中の身としては、技術ネタの多いこのインスタンスは大変貴重でありがたい。

今は試験公開中ということだけど、そんなにほんと大盛り上がりとかはしなくてよいので、細く長く続いてほしいなあと思っています。

ノンプログラマーに求められるもの

ノンプログラマーがプログラミングを覚えることにより普段の業務負担を軽減させる、みたいな流れが徐々に活気を帯びてきたというか、この本なんてその象徴のようにも思えるのだけど

www.oreilly.co.jp
ふと我が身を振り返ってみると、ぼくが趣味でプログラミングをやってるのって、そういう理由というか目的というかモチベーションではあまりなくて、もちろん、そういう効果があれば嬉しいし、実際にはあちこちで発信しているように、プログラミングを習得すればするほど「今までこの作業ってVimPerlやGitを使わずにどうやってたんだっけ……思い出せないし思い出したくもない……」と思うぐらい様々な負担が軽減、またはそもそも不要になっていたりするのだけど、でも気持ち的に、なんでプログラミングをもうかれこれ4年近く続けてやってきたのかっていうと、それはたぶん

《日々の業務があまりに地味かつ長期的で、目に見える効果というものがわかりづらくて、それはまるで人が年齢を重ねるように「昨日と今日の違いがほとんど認識できない」ものだから、そういうものとはまったく逆の「一瞬で目の前の世界がガラッと変化する」ような体験を求めて、プログラミングとかテクノロジーとかに希望を託している 》

ということなんじゃないかなあ、と思った。

言い換えると、日々の業務の負担が減るっていうのはたしかに嬉しいことで、村上春樹いうところの「小確幸」なわけだけど、じゃあそれがわざわざ貴重な時間を割いてまでやる「目的」とか「欲望の対象」になるのかっていうとそれにはちょっと弱くて、欲望の対象としてはだから上記の「これまでに見たことのない風景を見てみたい! 味わったことのない気分を体験したい!」みたいな気持ちがあって、結局はそれに向かって一生懸命コンピューターに向かい合ってるという感じがする。

それはほとんど人生を賭けた、生きる意味やテーマのような営みだから、その手段はプログラミングでなくたって構わないとも言えるんだけど、さしあたって現時点ではその過程にプログラミングがあるからそれに時間をかけている、というような。

でも実際には、プログラミングというのは自分が手を動かして英数字などを打ち込んでいかなければ(少なくとも2017年の現時点では)身についていかないわけで、頭の中で「こうなってほしい!」とか念じるだけでは出来てくれない。

何を言いたいのかというと、これってやっぱり一朝一夕に習得できるものではなくて、どうしても時間はかかるということ。

で、それって何に似ているかというと、たとえばスポーツとか、語学とか、あるいは楽器の練習。高い楽器を買って、さあやるぞ! となってもそれが身につくまではずっと自分の体を動かしてくり返し練習しなければならないわけで、さらにはそれを「趣味」でやるということは、普段の仕事は仕事として頑張った上で、その合間に、つまり本来なら娯楽を楽しんだりゆっくり体を休めたりできたはずの時間をつぶしてそれをやらなきゃいけない。

さらに言うなら、結局はそんなふうにわずかな時間を繋いでやっているだけだから、プロとしてやっている人たちとの差はみるみる開いていくわけで、いやそんなの当たり前なんだけど、でもどうしたって「自分より年下のあの人はあんなに出来るのに、なんで自分の上達はこんなにも遅いんだ……」って比べて思ってしまうのもまた自然というか。

つまり、趣味でやるっていうのは結構大変。上達の実感はいつも理想の遥か後ろを遅れてやってくるし、当初憧れたようには全然できないまま時間がどんどん過ぎていく。

それでもぼくがここまでそれを続けてきたっていうのは、だから「日々の業務の負担を軽減したいから」というだけのことではなくて、その向こうにある「なんか知らんけどスゴイ体験をしたい!」という感覚に揺り動かされてきたからだと思う。

そのある種の貪欲さというか、しつこさというか、諦めの悪さみたいなものに引っ張られてダラダラやっているうちに、結果として「なんか知らんけど業務もめっちゃラクになってる〜〜!」みたいなことがあるのかなと思う。

そんなことを踏まえつつ、冒頭に挙げた本のタイトルを見て思うのは、「コンピューターに退屈な作業をさせよう」なんて考えている自分自身はけっこう楽しい(退屈ではない)のだよな、ということ。

より具体的に言うなら、コンピューターにその作業をさせるためにちまちまコードを書いていく作業はけっこう楽しい。

さらに言うなら、そのコードを書く過程で素人なら必ずハマる。何度書き直してもうまく動かない。ちょっと、というかかなりイライラする。そして自分は自分で思っていた以上に馬鹿なんじゃないか? という底知れぬ不安に襲われる。もう何度も棚から取り出しては戻した参考書の、ついさっき見たはずの正規表現のルールをもう忘れてしまって、誰にも向けられない憤りにうんざりしながらまた棚から引き出す。頭はぐちゃぐちゃに溶けて煮詰まっているけれど、なんか踏ん切りがつかなくて深夜まで同じ箇所を修正し続けてしまう。そしてあるとき、突然視界がひらけるようにプログラムが動く。…おお〜〜〜〜!!ってなる。……でも周りには誰もいないから、実際にはその叫びは頭の中だけに響いてる。深夜の静かなリビングで、自分とターミナルの黒い画面だけがある。

その無音なんだか轟音なんだかわからない一瞬の魅力に囚われてしまって、その景色をまた見たくて続けてるのかなっていう。

上では何度か「趣味」と書いたけど、だからこのような日々を送るぼくにとってはプログラミングを「趣味」と呼ぶのはちょっと違和感がある。
もちろん仕事なわけではないし、ましてや天職とか使命とかいう大げさなものでもない。

そうではなく、なんか本職でも娯楽でもない、「2つめの仕事」みたいな感じというか。
日本語ネイティブの人が英語も日常生活を送れる程度には喋れる、というときの「第二言語」としての英語みたいな。

そういえば最近、ラムダノートの鹿野さんが書いた以下の記事で、
employment.en-japan.com

IT系エンジニアとして仕事で使っているプログラミング言語ではない言語、つまり第二言語を学ぼうというシリーズ記事

というのが始まっていたけど、ぼくがここで言うのはそれの人生全般バージョンというか、能力としての第二言語、本職を支える副次的な素養みたいな。
そういうものとして、育てていけるんではないかなあ、という感覚が少しある。

その意味では、こういう自分のような人たちを「ノンプログラマー」であれ「非エンジニア」であれ、語の頭に否定形(ノンとか非とか)を付けた呼称で表すことには以前から違和感があったけど、たとえば「セカンドプログラマー」とか「オルタナプログラマー」とか、ぼくだったら編集をやっているので「エディター・プログラマー」とか、「*er Programmer」とか(読めない)、なんかそういう本職の方にフィーチャーした言い方がないかなあ、という気もしてくる。

「趣味」とか「アマチュア(愛好)」ではないのだよね。べつに愛でてるわけではなくて、自分だって早く風呂に入ってさっさと明日に備えて寝てしまいたいんだけど、どうしてもさっきwhileで無限ループした理由が腑に落ちなくて、何度もいろんな場所の変数の中身をプリントデバッグしていたらもう外が明るくなってきたとか、そういうのは愛好ではなくてもう狂気に近い。というか非常識。

上で挙げた鹿野さんの、以下のインタビューも面白かったんだけど

itpro.nikkeibp.co.jp

その最終回にあった以下のくだりが印象的で。

私がオーム社でやっていた本の作り方は、会社から見れば特殊な作り方です。私しか作れない本が増えてきてしまった。会社からは「ほかの社員もできるようにしてほしい」と言われて広めようとしたのですが、うまくいきませんでした。私たちと一緒に仕事するときはバージョン管理などの仕組みを使ってくれるのですが、そうした社員も自分から使おうとはしなかった。

この最後のところ。「自分から使おうと」するかどうか、というのは、ようは上に書いたような「気づいたら時間を忘れて夢中になってた」みたいな感覚があるかどうか、ということだと思う。

そういうのがなきゃ駄目、という話ではなくて、というか誰にだってそういうのはあるはずで、単に対象がプログラミングかそうでないか、という違いがあるだけだろう。

とくにまとめもオチもないのだけど、つまりはプログラミング、結構大変。ぼくの場合は大学も美大で、授業でプログラミングとかなかったし、初めてパソコン&ネットに触ったのは28才で、初めてハローワールド!出したのは38才。だから世代や環境が違えばまったく上とは別のことが言えるかもしれないけど、いずれにしてもこういう人がこういうことを続けるには、「目的」とか「目標」とか「覚悟」とかではなくて、自分でもコントロールできないそういう非常識さというか、過剰さみたいなものが必要なのかなという気がしている。