シェルスクリプト Forced Todo

Forced Todoはなかなか進まない進捗に対して、一定の時間でTODOを読み上げ、進捗を迫るものである。 あまりに進捗しないので作ってみた。

Gist

Systray

Notifyを開始するとSystrayに常駐し、Systrayから終了できるようにした。

Systrayへの常駐はMailDeliver2同様、yadを使うようにした。

notifypid=$!

yad --notification --image=player-time --text="TODO NOW!" --command="yad --text-info --timeout=15 --title=TODO --width 300 --height 300 --filename=$HOME/.yek/forcedtodo/todotext" --menu='Edit!'"$task_editor $HOME/.yek/forcedtodo/todotext"'|Quit!quit' --tooptip="Immidiately Task" 
kill $!

Yadのオプションについて分析してみよう。

オプション 意味
--notification Systrayに常駐する
--image=player-time Systrayのアイコンを選択
--text="TODO NOW!" Notificationの名前(ポップアップに影響)
--command 左クリック時の実行コマンド
--menu 右クリックメニュー。項目名とコマンドを!で分けている。quitで終了

ネストされているYadは次の通り

オプション 意味
--text-info テキストダイアログ
--timeout=15 15秒でダイアログが閉じる
--title=TODO ウィンドウタイトル
--width 300 ウィンドウの幅
--height 300 ウィンドウの高さ
--filename=... テキストの内容となるファイル

Zenityとは結構な違いがあることに注意。

Yadは通常ブロックするため、Yadが終了しなければ戻らない。 そのため、Notifyするためのプログラムをサブシェルでバックグラウンドプロセスとして実行し、そのプロセスID($!)を取得し、Yadが戻ったらKillすれば良い、ということになる。

Notify (Open JTalk)

Notifyは単一のプロセスIDを振るためにサブシェルで実行し、バックグラウンドプロセスとすることでループさせる。

(
  while sleep $notify_interval
  do
  perl -pe 's/\n/。/g' ~/.yek/forcedtodo/todotext | open_jtalk "$jtalk_tuning[@]" -m $hts_voicefile -x $mecab_dictdir -ow $jtalk_tmpfile
  play $jtalk_tmpfile >| /dev/null &|
  notify-send -t $notify_time "TODO" "$(<$HOME/.yek/forcedtodo/todotext)"
  done
) &

単純なsleepによるループである。

基本的な手順としては

  1. Open JTalkで音声ファイルを作成
  2. 合成した音声ファイルをバックグラウンドプロセスとして再生
  3. Notify Sendを使って通知に表示

となる。

ここで大きな情報はOpen JTalkだろう。

Open JTalkはスピーチシンセサイザーだが、eSpeak, SVOX Pico, Festivalといったソフトウェアと比べて非常に自然で、日本語に対応している。 ただし、かなり複雑なソフトウェアだ。

Manjaro(Arch)では、AURにあるopen-jtalkパッケージをインストールする。

NAIST JdicファイルはAURにも存在していないが、辞書ディレクトリとして/usr/share/open-jtalk/dic/を使用することができる。精度は異なるかもしれない。

NAIST JDicをインストールするためには、mecabをインストールし、さらにNaist JDicで/usr/libexecが指定されている部分を/usr/libに変更しなくてはいけない。

女声を使用するためにはopen-jtalk-voice-meiをインストールする。

Open JTalkは非常に自然で、Twitterの読み上げにも使えそうだが(twとの組み合わせがいいだろう)、複数行を扱うことができないため、Perlで変換している。 また、Open JTalkは標準出力に吐くことはできないようだ。

教育と職業スキルと実用から見たプログラミング言語選択

前articleの続きだ。 なお、Swiftは使ったことがない。

Perl

オワコン的に言われることも多いが、柔軟で強力な言語だ。 今や仕事にはまずならないが、コンピュータを便利に使ううなら覚えておいたほうがいい。

シェルスクリプトで悩むより、perl -e(perl -neperl -peも)が早いことは多いし、Windowsでは作業スクリプトを書くのがとても楽になる。

性的型付け言語だが、数値・文字列間では動的で全体的にゆるく、学習もしやすい。 ただし、I/Oまわりと文字列処理は独特で、合理的すぎて人間の理解の先を行く感もある。説明はしやすい。

Python

割と有望な言語。ライブラリも豊富で色々やりやすいし、比較的楽ではある。

好き嫌いは割れる。Python的な正しさを求められるので、正解が決まっていたほうがいい人、統一されていてほしい人は好きだし、好きにやりたい人は嫌いだ。

文字列処理が苦手なので、特に日本人で文字列処理をしたい人には向いていない。

構文が独特なので学習には向かない。 仕事のクチはまぁまぁ多い。

PHP

「オワコン」「ダメ言語」。 元々の目的から外れて拡張された結果、ひどい継ぎ接ぎと矛盾になってしまった。 PHPは便利ともてはやされ、なんでもPHPに求めた結果、PHPでないものを要求されたのだ。 だから、プログラミング言語としてはかなり苦痛な部類に入る。

もちろん、本来PHPがするべきことをする(HTMLの一部を動的に生成する)ことには適している。 また、WordPressなどのPHPアプリケーションをいじるのにもスキルは必要になる。

構文自体は普通なので初歩を学ぶにはいいが、普通はもっと柔軟に書ける。 仕事のクチは多いが、一時はPHPだらけだったのでPHPエンジニア(ペチパー)の数が多く、口数は減っているので参入は結構きつい。

Ruby

最も稼げる言語。 柔軟さ、読みやすさ、強力さを兼ね備える。 日本人の作なので、日本語文字列の扱いが楽なのもメリット。 実行が遅いという欠点も、だいぶ埋まってきた。

隙のない言語だが、作者が言語マニアで、あまりメジャーじゃない言語を参考にしていたりするので、構文に普通でないところがあり、初歩学習には向かない。 また、GUI関連ライブラリが弱い。

稼げるのはRuby on Railsの話しで、Ruby全般に詳しくてもクチは少ない。

java

冗長な言語。 親hackerのSunが嫌hackerのOracleに買収され、Oracle次第になっている。

「javaのバージョンによる違い」に振り回されるがコンパイルされたJavaコードが異なるシステム上で動くことは多く、Java用GUIツールキットもあるためアプリケーションを書くのに便利。 しかも速い。 冗長でもめんどくさいことと、バージョンに振り回されるのが欠点だ。

日本では仕事のクチは最も多い気がする。 給料も高い。

C#

Microsoft版Java。 Javaよりもちょっと気が利いているが、だから良いとは言えない。

まず大抵Win32APIを叩くので、Windows用C#コードはWindowsでしか動かないし、Windowsのバージョンにもよる。

だから仕事のクチも少ない。

JavaScript

Javaとは関係ないが、Javaっぽくも書ける。 小さいが、言語設計を知る人なら舌を巻く傑作である。

シンプルで標準的な構文で、簡単なことを簡単に書けるが、かなり本格的な記述もでき、hackしがいもある。 DOMを使ったアクションで視覚化しやすいことを含めて学習には向いているが、オブジェクト指向部分はプロトタイプベースというキワモノで、実際便利だが、独特。

JavaScript自体がウェブブラウザと一体なわけではないが、実際はほぼウェブブラウザ上で使われるため、ウェブクライアントサイドスクリプトという独特な使われ方と共に内容も独特になるし、仕事ではそれが苦労にもなる。 一方で、そのために言語処理系の用意が簡単でハードルは低い。

ウェブをやりたい人は通じ用の言語とは別口で学ぶ必要が生じる。

仕事のクチはとても多い。

C

現用で最も原始的な(つまり大変な)言語。 現在は速度を求められるケース以外では使われなくなってきている。

意外と学びやすい(静的な言語としては)が、ポインタでつまずく人が多い。「値か参照可」はいずれ理解する必要があるにしても、メモリアドレスを直接障るのはPerlと比べてもハードルが高い。

Linux開発者になりたいのであれば避けては通れない。

C++

Cを使いやすくしたものだが、Cより邪悪だという人も多い。 Simula由来のオブジェクト指向は現代的でなくハードルは高い。

OSSでの採用率は高いので、OSSで活躍したい人は学んだほうがいい。

D

CもC++もJavaも嫌な静的派・速度派の人のための言語。 どちらかというとこれらを学んだ人のための言語でDからはじめめようという言語ではないかもしれない。

仕事のクチはほぼない。

Go

CもC++もJavaも嫌な静的派のDでない選択肢。 並列実行が楽で、あまり普通ではないが、ErlangやHaskellよりはマシなので、現代的な多プロセッサコンピュータの性能を活かすプログラムを書くための有力な選択肢となる。

初学には向かず、仕事のクチも稀。

Swift

iOSアプリの正式言語。 iPhoneだけを信じている人なら良いが、iOSアプリはSwiftでなくても書けるので、そこまで人気はない。

前代のObjective-Cに比べればかなりマシな言語で、iOSアプリ開発も楽しく行える。

仕事のクチは少ない。

Lua

PHPに似ている小さな言語。 小さいがよくできてきいて、プラグインなどによく使われる。 Lua JITの実行速度が速いことでも有名。

後発の言語だけあって既存の言語をよく研究しており、構文は現代的で記述も楽。

プラグインが自分で書けることを含め、使えれば実用性はあるが、仕事にはなりにくい。 初学には向いている方だと思う。

COBOL

古代言語。 当時から嫌われ者だった。変に冗長で英語っぽい言語で、今となってはすごく変な言語。滅ぶべき言語の筆頭とされているし、学びにくい。

ところが、金融領域で使わされているため、全然滅びない。 一定の需要があり、しかも若い使い手は稀なので、あなたが若いならマスターしておくと当面食いっぱぐれないかもしれない。

プログラミングことはじめ

既に何度か言及しているように思うけれど、Journalでは言っていないかもしれない。

プログラミングを、職業的スキルとして捉えて、はじめようとか、どうしたらいいかという人が多いのだが、ちょっとまって欲しい。 それは「野球って儲かるらしいから野球をはじめようかな」と言っているのに近い。

ブラックな労働環境で知られるIT業界だが、実際にブラックなところは多いし、それが当たり前になっているとも思う。

IT業界でプログラミングを行うのは、下級作業員であるコーダー及びデバッガ、プログラマ(PG)、そしてシステムエンジニア(SE)である。 コーダーは言われたものを打つだけだし、デバッガは書かない。管理監督者であるSEがコーディングするようだと人員が足りていない。

プログラミングの世界では、単純作業であるコーディングと、創造的なプログラミングを分けることもある。 コーディングでは特にスキルはいらないため、「IT土方」なんていう言い方をしたりもする。 日本のIT企業では(代わりの利きやすい)均質化を求められるため、PGやSEの仕事でも創造性は乏しい。

そうして部品のように扱われ、ひどい待遇を受けても、彼らは「プログラミングは楽しい」のである。 時給百円程度で使い倒されるアニメーターにも通じる部分で、いくら不況の就職難でも、プログラミングが楽しくなかったらやめているという人は多い。

基本的に彼らはスーパースターではなくても、好きで、オフにも勉強会に行ったり本を読んだりする人たちだ。それに対して「プログラミングができれば仕事がもらえる」という考え方はあまりに浅薄ではないか。

辛い仕事だからこそ、好きか否かが問われる。やってみることは良いことだが、プログラミングはそんなに浅くないし、退屈でもない。そして向き不向きがはっきりしている。

向き不向きを分けるのは、知的好奇心と制御フリークかどうかだ。自分が書いた通りにコンピュータが動くことに感動するのか、「ふーん」と思うのか、まだ自分ができない、やり方を知らない技を「どうやってやるんだろう」と思うかどうかは決定的な違いになる。

関心がわかない人はどこまで行ってもプログラミングには向いてない。そんな人が技術職についても人生が辛くてもったいないものになるだけだ。

まずはプログラミングを体験すること、そしてプログラミングの「センス」を作ることだと思うので、余計なことを書かなくても動き、汚いコードでも間違っててもできるだけ動き、現代のプログラミング言語に共通する構文で、できるだけ簡単にフィードバックを得られるかというのが言語選択のポイントだと思う。 私はJavaScriptがイチオシ、PHPやPerlも良いと思う。私の好きなRubyは、オブジェクトへの理解が必須になるので、このあまり教育向きではないし、C, C++, c#, Javaが良いとは全く思わない。

もちろん、「プログラミングってこういうものか」と分かってくれば好み相性で本格的に学ぶ言語を選べばいい。 とにかく、まずは楽しめ、プログラミングで遊べ、というのが私からのアドバイスだ。

では、いくつか例示してみよう。「画面にHello, world!と表示する」のは伝統的な最初の一歩だ。次のコードはPerlでもRubyでも動く。

print "Hello, world!\n"

少し気を利かせて、名前をきいてみる。Rubyだ。

puts "あなたの名前は?"
name = gets
puts "Hello, #{name.chomp}!"

Perlだとこうなる。my $nameと言っているが、内容はyour nameだ。

print "あなたの名前は?\n";
my $name = <>;
chomp($name);
print "Hello, $name!";

もうちょっと気の利いた例だ。 別にHTMLを用意する必要があるが、ボタンを押すと「私のことは好きですか?」ときき、「OK」「キャンセル」が出る。OKなら終了だが、そうでないと「えー、またまた」と表示し、くりかえすJavaScriptだ。

var btn = document.getElementById("TheBtn")
btn.onclick = function() {
  while(! confirm("私のことは好きですか?")) {
	  alert("えー、またまたー")
	}
}

ちなみに、2番目をRubyらしく「ちゃんと」書くとこうなる。

STDOUT.puts "あなたの名前は?"
name = STDIN.gets.to_s.chomp
STDOUT.puts "Hello, #{name}!"

Rubyらしく丁寧に書くのはこの規模では無駄もいいところだ。だから教育には向いていない。

class Hello
  def initialize
    name = ask
    hello(name)
  end

  def ask
    STDOUT.puts "あなたの名前は?"
    name = STDIN.gets
    name.to_s.chomp
  end

  def hello(name)
    puts "Hello, #{name}!"
  end
end

それでも静的型付け宣言やらのいるC, Java, Pythonなんかよりはマシだろう。

なお、文系理系の話もされるので言及すると、プログラミングは論理的思考力は求められるが、作業自体は文系である。 ただし、内容によっては数学の素養が求められるほか、チューニングなど理系の作業もある。

「文系プログラマ」を侮蔑語として使うエンジニアもいるが、その人は(自分で書いたコードでなく)出身学科くらいしか自己を立脚するもののない憐れな者なのであり、そのような人が文系である私より良いコードを書くのは稀であるから心配はいらない。

強いて言うなら、自分で小説を書く人や、絶えず新しいアタリの本を探している人は向いているし、夏目漱石を賛美するだけの人は向いていない。

MailDeliver2に「確認できる着信通知」

動機

Cinnamonの通知機能は使いにくい。

通知はずっと残すか(どんどん増えて超重くなる)、表示が終了したら消すかしかない。
メールの着信通知をNotify Sendで出しているのだけれども、見逃していまうと確認しようがないと、通知が常時きてるような環境ではその時に見られないので、まず確認できない。

着信に気づいてから確認したいのだが、その機能がなかった。
そこで、かねてからの希望だった、「Systrayに常駐し、クリックするとこれまで受け取ったメールが確認できる」という機能を追加することにした。

30分くらい、と思ったが5時間ほどかかった。

苦戦

まずSystrayをどう実現するかだが、yadを使った。
Zenityのforkらしいのだけれども、なんかだいぶ違う。

yadをどう使うかというのは随分悩んだ。
yadの--commandではクォートがエスケープされてしまうため、スペースで必ず分割されてしまう。そのため、置き換えが発生する部分は別にくくりだすしかない。
--listenで実行するコマンドを随時更新できるが、ここでも同様の問題があるし、そもそも標準入力から渡す方法というのは結構限られる。

結局3つに分割している。データ整形用のスクリプト、yadのcommandになるスクリプト、そしてyad自体を起動するスクリプトだ。

当初、4つだった。一時的に保存され、通知と共に消されてしまうヘッダをとっておくためのプラグインだが、不毛なので(そしてそれが必要になるプラグインはおそらく多いので)Maildeliver本体に組み込まれ、LeaveHeadersという設定を可能にした。

時間がかかったのは細かいミスが重なったわけで、未知のエラーには遭遇していない。

いずれにせよ、これでメールを遡って確認できるようになった。ダイアログのOKでクリア、cancelで保持だ。

Yadの起動をRubyで行っている理由は結構単純で、複雑なエスケープなしにスペースやspecial characterを含むエントリを分割するために行志向にしていて、行単位でargを与えるのがRubyだと楽だから。

Yadのspecial character

結局yadでなくZenityを使っていたのは、Yadはエントリーに対して結構複雑なエスケープを必要とするためで、とりあえず手っ取り早い方法としてである。

簡単にいえば、Yadの--listでエントリが表示されなかったり、ひとつ上の内容が表示されたりしていた。
ドキュメントにないので自力で試した限り

  • <または&が含まれる場合、そのエントリはテーブル的に上の項目をクローンする
  • エスケープ方法はXML(&lt;及び&amp;)
  • ところが&rt;には対応しておらず、&が含まれるためクローンされてしまう

こういうわけのわからない仕様はやめてほしい。

シェルスクリプトで並列実行制御を行う

シェルスクリプトを書く場合において、処理を順次おこないたいことは多いはずだ。
多くのディレクトリや処理のリストなど、順に処理を適用していくケース。

まず、この設計だが、「ある特定の場所でしか実行されないスクリプト」はnoexecが指定されていない限りはそのルートに設置すべきだと思う。
対象ディレクトリごとにスクリプトが異なってくる場合はその対象ディレクトリごとにスクリプトを設置する。
もしそうでなく、その違いがディレクトリのパス自体に含まれるのであれば、単純に対象ディレクトリに空のドットファイルを置けばよいと思う。

例えば、…/.somescriptを実行するのであれば、トップに

for i in **/.somescript
do
  ( cd ${i:h}; zsh .somescript )
done

のようなスクリプトを書けば良い。Gitが「Gitを実行するディレクトリがリポジトリになる」という仕様なので、このように方法にらも随分なれてしまった。
もし、パス要素自体が重要になるのであれば、

for i in **/.target
do
  fooscript ${${i:h}:t}
done

という方法も考えられる。

それは良いとして、その各処理が時間がかかるとしたらどうだろう?
これは別にディレクトリ単位であった場合に限らない。テキストファイルに1行1エントリ形式で書いて読みながら処理する場合も同様だ。

別に最初からターゲットをグループ分けしてもいいし、xargsを使って3つずつ実行する、といったことで並列化することもできる。
だが、できれば常に3スレッドで実行する、といったモデルのほうが好ましいのではなかろうか。

これを実現するためにシェルスクリプトで並列実行制御したいのだが、残念ながらこれはかなり難しい。
Zshでもzthreadがあるような話も目にするのだが、まぁ実際はそうもいかなそうだ。

並列実行の難しさは、「同時アクセス」にある。あるリソースに同時にアクセスした場合、いろいろな形でおかしなことになる。
ファイルデスクリプタを共有すれば良いのではないかと考えたのだが、

worker() {
  workern=$1
  typeset val
  while read val
  do
    print "Worker $workern: $val"
  done
}

print -l {1..100} | (
  for n in {1..3}
  do
    worker $n &
  done
)

wait

結局read時に同時アクセスするとおかしな値を取ることになり(空文字列だったり、複数行がぐちゃぐちゃに混ざったものだったりする)、ちゃんと動作しない。

なお、ここでのポイントをまとめておこう。

  • ()はfork子プロセスを生成し、子プロセスで実行する。
  • この子プロセスに渡されたパイプは、子プロセス自体の標準入力として受け取ることになる。リダイレクトしないプロセスはこのファイルデスクリプタを共有する
  • &でバックグラウンドで実行する。子プロセスを生成したかどうかは関係がない
  • 外部コマンドはfork+execで子プロセスを生成するが、関数は生成しない
  • waitはジョブを共有待ち合わせる。引数なしですべてのジョブを待つ
  • 同じファイルデスクリプタを共有している場合、IOの位置もいずれかのプロセスが動かせばすべてのプロセスで動く

結局、アクセスしたら要素をひとつ返してくれるQueueがほしいのだ。

そこでまじめに考えてみた。一番単純なのはflockを使う方法なのだが単純にはいかない。プロセスの中で処理したければ、ファイルデスクリプタを使った、複雑な方法が必要になる。
その中で比較的スマートに処理できると考えたのがGistのスクリプトだ。

この場合、.lockは空ファイルであり、単なるロックでしかない。無駄なファイルを作るのにはちょっと抵抗があるが、方法としては比較的簡単だ。
この方法はbashでもほぼ同様に書くことができる。あまりzshらしい方法とは言えない。

なんか悔しいので、Zshらしい方法として、Socketを使うという方法を提案してみる。
UNIXドメインソケットはファイルパスを用いてプロセス間通信を行う。TCP同様、サーバーが接続を受け付け、IOを確立するものだ。

一般的にはサーバーは並列処理できるように接続の受け付けはマルチスレッドで行う。

zsocket -l foo
integer server=$REPLY
	
while zsocket -a $server
do
(
  integer io=$REPLY
  ...
) &
done

だが、シングルスレッドで行った場合はどうなるか。listenはしているがacceptはしていない状態だと接続しようとするclientはブロックされ、acceptされるまで待たされることになる。
結果的に、あるリソースにアクセスし、供給する部分がシングルスレッドになる。同時にアクセスしてもproducerはそれをブロックして順番に渡していくことになる。

これはごく普通のマルチプロセスモデルであり、Perlはthreadが非推奨で、UNIXドメインソケットの利用を推奨している。
そのため特に目新しいものではないのだが、Zshでおこなう(シェルスクリプトで行う)というとまたちょっと味があるのではないか。

Gist

文系プログラマはそんなところで挫折しない

原文に言及

Paizaのプログラミングの勉強を始めたときに、文系が挫折しやすい7つのポイントという記事に猛烈な違和感があり、ついでなので巷に溢れる理系だ文系だ文系プログラマだとかいう風説を否定していこうと思う。

既にTwitterで言及されたりしているのだが、明らかに「プログラミングの勉強を始めたときに、文系が挫折しやすい7つのポイント」ではなく、「プログラミングの勉強を始めたときに、文系の私が挫折した7つのポイント」だ。

予め言っておこう。私は文系である。脳はバリバリの理系思考だが、興味分野は文系であり、教科としての得意分野も文系科目なので、世の中的には文系ということになる。だが、私は2歳の時にはプログラミングをやっていたし、幼少期は際立って天才的だったのだが、それでも言語発達よりもプログラミング習得のほうが早かった。ひらがなをマスターしたのは4歳になってからなので、ひらがなよりもアルファベットが先にあった。

Hello, World!

別にそのようなことは微塵も思わないし、それは文系か理系かということとは全く別次元のところにある。

簡単にいえば、「テレビが大好きな人はプログラミングに向かない」のだ。

テレビを視聴している時、脳は停止している、ということが既に研究により判明している。だが、おそらくこれは真実ではない。私はテレビを観ていようが、非常に活発に様々なことを考えている。そして、その速度に追いつかないことや内容が稚拙なこと、私の疑問に応えないことにいつも苛立っている。だから、私はTVが大嫌いだ。

対して、ゲームが好きな人は向いている。ドライブなら助手席が好きな人より運転席が好きな人が向いているし、絵を見るのが好きな人よりも絵を描く人が向いているし、ラジコンが好きな人はかなり向いている。

要は自分が支配し、操縦することが好きな人は向いていて、受け身な人は向いていない。

さらにいえば、好奇心の多寡が重大な違いにもなる。

この節において、この人は単に受動的すぎて、しかも好奇心によって駆動してもいない、テレビが好きなタイプであるために受け入れられなかった、というだけだろう。

だいたい、そもそもこれを「暗記する」と考える時点で向いていない。「どうなっているんだろう」と思ったあなたは向いている。

#include <stdio.h>

int main(void)
  {
      printf("Hello, world!");
      return 0;
  }

おまじない

これは、よく使われるが、よく知られた悪しきものである。
単にきちんと説明する力がないのだろう。理解させる必要がないのかもしれないが。

C言語の#includeは、含める、そのまま「取り込み」に用いるものだ。stdioはSTanDard Input/Outputであり、読み込んだり、書き出したりする機能を持っている。
この機能を利用するために

#include <stdio.h>

と書いておく。

C言語の基本部分はただの骨組みだ。機能は別パーツになっている。これを取り付けるのがこの作業だ。

「何でも入る型ひとつあればいいのでは」は、疑問としては正しいし、意見としても構わない。汎用型を持っているものもあるし、Perlは静的型の言語だが、スカラー変数にはスカラー値であればなんでも入る。

これは思想的な問題である。C言語がそうなっているだけだ。
もちろん、メリットはある。気になるならそれを調べればいいし、そんな簡単に思いつくことは誰かが試している可能性が高いのだから、それを調べてもいい。

だが、躓きはしないだろう。学習を困難にさせる部分であり、なるべく型を意識させない言語というのは、私がプログラミングを教える上で重要な条件にはなっているが、別にそれは文系だからという問題でもない。

なお、なぜ型があるか?という質問に対する応えは明瞭だ。

トマトは冷蔵庫にいれなくてはいけないが、猫を冷蔵庫にいれるわけにはいかない。別な扱いが必要だと区別する必要があるだろう?

セミコロン

これも、行の切れ目が文の切れ目ということでいいじゃないか、はそのような思想の言語はたくさんある。

Perlは常にセミコロンで終端する。各種UNIXシェルは通常、行末を文ターミネータとして扱う。

RubyやJavaScriptは、「文脈的に次が続きであれば続き、そうでないければ終端とみなす」という扱いだ。

どれも一長一短だ。Perlの統一はわかりやすいが、同時に忘れやすい。

my $a = "Something";
print $a;

シェルの分割されるのは思わぬトラブルの元だ。

text="Hello,
This is Aki!"

これはエラーになる。でもZshではエラーにならない。

Rubyであればこういうこともできる。

array = [ "a", "b",
          "c", "d"]

JavaScriptも同じようにできるが、シェルで分割される文字列の改行は許されない。

よほど変なルールならともかく、C言語の場合はだいたい「セミコロンをつける」で統一されている。
それを「つまずく」と言っているのは、もはや言いがかりをつけたい気持ちであるだけなのではないか。

ループ

ループが面倒だというのはあって、そのあたりRubyが素晴らしい処理を行っている。JavaScriptライブラリもそれにならったものが多い。

「何を意味しているんだ」というのをつまずくと言ってしまうのならば、それは学習意欲の欠如を恥じるべきだと思うけれども。

3 part for loopは結構わかりにくいので、工夫が必要だ。

for (a = 0; a < 11; a++) {

11回ループなのだからそれほどわかりにくいこともないと思うが、別に1 originにしたいのであれば

for (a = 1; a <= 11; a++) {

とか至って構わないわけだ。もちろん、これはわかりづらいので、Rubyはこのループを排除している。

11.times do |a|

だが、この3 part forが欲しいケースは、全然違うところであったりする。まつもとさんが大嫌いなので、Rubyに入ることは絶対にないが。

for (<>; ! ~ /^END$/; <>) {

Perlになれている人以外はまるでわからないと思うが、これは、「各行を読み、それが「END」だけの行でないならばループを続ける」ということだ。

この応用として

for ( my @lines; ! ~ /^END$/; <>) { push(@lines, $_) }

というスマートなループが書けたりする。

まぁ、こんなものはお遊びだが、ループする必要性は簡単だ。日常的に、「食べ終わるまでごはんを食べるのとおかずを食べるのを繰り返す」といったループを、送っているはずなのだから。

配列

1クラス40人いたとして、出席簿を40冊用意してそれにタイトルを書きたいか?「出席簿」というタイトルで40人並べるほうが絶対楽だろう?

それだけのことだ。というか、配列なんていくら文系でも数学で習うだろう。

配列でつまずくとかわけがわからないよ。

ポインタ

確かに難易度は高いし、もっとよしなにやってくれと思うところではあるけれども、結局それが参照なのか実体なのかというのは常に問題になるのだ。

だが、これは文系がどうかには全く関係がない。文系だって「Aさんが佐藤だと思っている人物と、Bさんが鈴木だと思っている人物は、同一人物である」みたいな文章が出てくれば理解できるはずなのだから。

まとめに対して

本人も意欲のせいだということを言ってはいるが、そのあとがおかしい。

「なぜなのか?どうなっているのか?なんのために?どういう意味?」それを気にするのは理系の傾向であって文系の傾向ではないし(理系は証明する学問だ)、非常に重要な資質なのだ。

「そういうものだからで済ませる」というのは、明らかに向いていない人だ

それは、理系だの文系だのという話ではなく、理知に対する姿勢だと思う。どうでもいいことばかりを考えて難癖つけたがるというのは、もっと単純に「意欲がないし、取り組む姿勢もない」というだけの話で、文系に対する風評被害を生じさせるのはやめていただきたい。

実際文系プログラマってどうなの

プログラミングなんて9割方文系領域なので、別に何も問題ない。

クリエイティブであるはずの料理を単純作業にできるように、世の中ではプログラマの仕事が単純作業だったりもするわけで、そこはスキルなんてほぼ不要だったりもする。やる気なんかなくても覚えたことを繰り返すだけでいいという世界もある。

優れたスキルが欲しいのだとしても、別に文系だから不利ということは特にない。理知的であれば良いのだ。

むしろ、私が持っている「記憶力」というハンデのほうがはるかに大きい。

JavaScriptなんて必要なら自分で書けばいいじゃない

JavaScript、特にJQueryを多用する人の中に、妙なプログラミングスタイルでありながら、それに文句を言う人が結構いる。有名所ではQiitaのコールバックを駆逐したいとか。

JavaScriptはメタプログラミングが容易なので、自分で好きにいじれよ、別にフレームワークに依存する必要もないだろう、と思う。

では、同記事の課題から。

$.get("hoge.txt", function(hoge){
    $.get("piyo.txt", function(piyo){
        console.log(hoge + piyo);
    });
});

$.getが非同期で第一引数のファイルを読み、第二引数の関数をコールバックとして呼ぶものであるらしい。
だが、$.getを使う前提であれば次のような関数を用意しておけばいい。

$my.gets = function(/ *** /) {
  var callback = arguments.pop
  var getfunctions
  var args = arguments

  if (arguments.length < 2 )
    return undefined

  getfunctions = function() {
    $.get(args.pop, callback)
  }

  while (args.length > 0) {
    getfunctions = function() {
      $.get(args.pop, getfunctions)
    }
  }

  getfunctions()
  return true

}

JavaScriptの無名関数はクロージャである。
外側にある同一のオブジェクトを参照することができるし、getfuinctionsに代入される無名関数に書かれたgetfunctionsは代入される前の時点のオブジェクトである。

あとは簡単

$my.gets("hoge.txt", "piyo.txt")

元記事はDRYに反するコードを連ねている。

メタプログラミング、リフレクション、ダックタイピング、そしてDRYを正しく意識を払えば、簡潔で美しいコードが見えてくるだろう。
上記の例はわかりやすさのために美しさを犠牲にした。

また、JavaScriptは配列も配列されていない。
例えば

a[0] = "Foo"
a[10000] = "Bar"

としたところで、10001個のメモリが確保されるのではなく、2個確保されるだけだし、

a.10000

のようにもアクセスできる。
lengthが問題にならないのであれば既存クラスにだって好きにいれて良い。
例えばHTML要素のオブジェクト(a Element)に対して

elem._param_src = foo

のようにしても構わない。
(Element要素などは元のプロパティが多いし、いつ新しく入ったプロパティとバッティングするかわからないので、その点を考慮した書き方をしたほうが良い)

どんなオブジェクトにも適用できるので、先の例も

$.get.seq = function() {
  / *** /
}

のようにすることもできる。こうした柔軟な発想も必要だ。

PureDocとKramdownのhn要素のidを統一する

「Markdownで書いたページがTOCのリンクが切れている」という問題に気づき、急遽対処を行った

問題は、Kramdownが生成するヘッダーIDが、単純にテキストを用いるものではなかった、ということだ。

KramdownはどうしてもIDにACSIIのみを使うようになっており、そのためになかなか複雑な処理をしている。

これを統一するため、Kramdownのこの処理をしているところを探したところ、base.rbにあるKramdown::Converter::Base#generate_idであることがわかった。

これを、常にテキストを用いるようにオーバーライトする。ほんとはオーバーライドしたかったのだが、Kramdownの構造的にそれは割と難しい。

PureBuilderのコードをいじれば、一応動くようにはなる。これは、そのままのテキストを使うようにして、PureDocと揃えたためだ。

Gist

PureDocのほうも複数の形式をサポート。その中でデフォルトは今までどおりテキストを使う形式だが、Kramdownに合わせ、重複した場合の対処を加えた。

これによってテキストだけではIDを判断できなくなったので、IDもTOCの中に含めるようにした。

Gist

新 Aki SI&Eサイト構築と、納得できないCSS解釈

作り直してるよという話はしていたが、ついに全面刷新となったAki SI&Eの作成に着手した。

昨日1日でlightboxプログラムを作り、さらにメニューボタンアニメーションを作った。

Lightboxアプリケーションは作った時点で1kBを切っており、相当軽量な内容になっていた。
JQueryなどは使っていない。

(function() {
  if (! document.addEventListener ) { return false; }
  
  var wrapper = document.getElementById("WrapWindow") /* ModalWindow */
  var fadingTimer = false /* IntervalTimer */
  var alpha = 0.0 /* ModalWindows's alpha number */

  /* draw content */
  var displayContent = function() {
  }
  
  /* fading out (interval callback) */
  var fadeout = function() {
    if (alpha < 0.8) {
      alpha = alpha + 0.05
      wrapper.style.backgroundColor = "rgba(0,0,0," + alpha + ")"
    } else {
      clearInterval(fadingTimer)
      fadingTimer = false
      displayContent()
    }
  }
  
  /* set this for event callback */
  var setLightboxTrigger = function(e) {
    wrapper.style.visibility = "visible"
    fadingTimer = setInterval(fadeout, 30)
  }

  /* Return from lightbox */
  wrapper.addEventListener("click", function(e) {
    if (fadingTimer) {
      clearInterval(fadingTimer)
    }
    wrapper.style.backgroundColor = "transparent"
    wrapper.style.visibility = "hidden"
    alpha = 0.0
  }, false)

  
  document.getElementById("SideNotes").addEventListener("click", setLightboxTrigger, false)


  
})()

基本的な作りはごく単純だ。
htmlbodyheight: 100%;を持っていて、

<section id="WrapWindow">
</section>

#WrapWindow {
  visibility: hidden;
  z-index: 10000;
  background-color: #000; /* for RGBa Unsupported browseres */
  background-color: rgba(0,0,0,0);
  min-height: 100%;
  min-width: 100%;
  position: fixed;
  top: 0px;
  bottom: 0px;
}

となっている。これにより見えていないし触れることもできないが見えている画面より手前に全体を覆うブロックがあり、これがvisibleになることで全体を覆って操作不能にする。

プログラム全体を関数で書こうことで、全体でアクセスできるプロパティを外側に伝播しないように閉じ込めることができる。「クロージャを作ることで変数をローカル化し直ちに呼び出す」というテクニックは、オライリーのJavaScriptで紹介されている。

グローバルな値としてfadingTimeralphaが定義されている。
fadingTimersetInterval()オブジェクトを格納するためのものだ。これをどこかにとっておかないとclearInterval()することができずに困ってしまう。

イベントが発生した時にfadingTimerが真か偽か、によって判断を変えることができる。
fadingTimerが真の状態で呼ばれる可能性があるのは、#WrapWindowに対するクリックイベントだけだ。
なぜならば、fadingTimerがセットされる前に透明なだけでvisibilityはvisibleになっているので、クリックイベントは必ず#WrapWindowが取る。もちろん、なんらかのブラウザの不備でクリックされる可能性はないではないが。
では、「これから真になる、まだ偽の状態で発生するか」というと、真になる関数が走った時点で、JavaScriptはシングルスレッドなので、真になるまで他のイベントを発生させても実行されないので関係ない。
操作に対するフリーズタイムを短くするためにsetTimeout()を使うこともあるくらいだ。

文字列(evalされる)の登録でなくコールバック関数の登録をするためには、intervalで呼ばれるコールバックで繰り返し使われる値を覚えておくことができないので、これはコールバック関数に対してグローバルでなくてはいけない。
そのための値がalphaだ。

1.0になれば終了なのでその時点でclearIntervalだが、まだインターバルタイマーが動作している状態で#WrapWindowがクリックされる可能性はある。これが真の場合だ。

では、その場合どうするかというと、インターバルタイマーの状態に関わらずオーバーレイを消す。そのため、インターバルタイマーが働いていればclearIntervalしてしまう。

なお、最後に追加しているイベントは本番用ではなくテスト用だ。

CSS

今回の目玉のひとつが、main, article, section, header, footer, asideといった「HTML5への対応」だ。
私のサイトにある大量のdivを少しでも減らしたいからなのだが、HTML5への対応は様々なメリットがある。
一方でレガシー環境を切り捨てることになりそうだったので慎重に対応してきたが、NN 4.6相当でも動作しそうな見通しがたったので採用となった。

それはともかく、納得できない振る舞いに随分悩まされた。

        <div id="SideContent">
      	<section>
             <nav id="Toc" class="toc marginbox_main contentbox">
               <h1>目次</h1>
               <ul>
                 <li>ページナビゲーション</li>
               </ul>
             </nav>
        ...
        </section>
        </div>

div, section, nav、あるいはそれらが内包する全てのテキストに対してfont-sizeの相対的変更(80%など)をすると、ボックスが上下とも縮まり、左カラムと位置が揃わなくなる。
インスペクタで見ると、table-cell要素の#SideContentの位置は正しいが、sectionのマージン上辺と#SideContentの上辺の間に隙間ができる。

また、中に入っているボックスの上マージンを削ると、ボックスの高さがそれだけ減ってその分上に隙間が空く。
配置は関係ないらしく、またこれはpaddingが設定されている場合のみ発生する。
paddingを設定したボックスに内包されるブロック要素のmarginをいじるのは、高さを意識する必要がある場合は厳禁ということか。

        <section id="MainArticle">
          <article class="marginbox_main contentbox">
            <h1>記事のタイトル=章</h1>
                ...
          </article>

このh1のマージンを設定するとその分articleが下がる。
h1より上にボックスを置くとボックスは伸びるが、その分右カラムが下げられてしまう。

恐らくは「h1を特別扱いしている」のだろうと思う。
だが、このためにh1を修飾することが非常に難しい。

結局position: relativeにしてずらした。上以外のmarginについてはこうした問題はない。

Paiza プログラミング彼女の解法と不満

Paizaで展開している、「プログラミング彼女」というサブコンテンツ。

めがね

Paiza B級。

ビットマップマッチングだが、値は

0 1 1 1
1 0 1 1
0 1 0 0
0 1 1 0

のような文字列で与えられる。マッチングパターンも同様だ。

結構馬鹿馬鹿しい。

その1

ip = []
tp = []

gets.chomp.to_i.times {|n| ip << gets.delete(" ") }
gets.chomp.to_i.times {|n| tp << gets.chomp.delete(" ") }
tp[0] = Regexp.new(tp[0])

ip.each_with_index do |ips, ipi|
  ips.scan(tp.first) do
    offset = $`.length
    if tp[1 .. -1].each.with_index.all? do |tpp, tpi|
                                          ip[ipi + (tpi + 1)][offset, tpp.length] == tpp
                                        end
      print "#{ipi} #{offset}"
      exit 0
    end
  end
end

これが通過しなかった。

手元でテストする限り、これで通過しないケースは発見できなかったのだが、case4で0.11secでこけてしまう。

これは納得できないところだ。

文字列の正規表現マッチングを行っているため、かなり魔術的に見えるだろう。
しかし、実はそうでもない。

「文字列」というが、Rubyの場合は文字列はバイト列も含む。
バイナリ検索にしてもバイトパターンのマッチングは普通なので、別にそのために文字列検索を行ってもなんら問題はない。

本来は多重配列を用いたマトリックスを求めていたのだろうけれども、Rubyの能力を考えれば配列よりも文字列のほうがはるかに扱いやすいし速いので、ちょっと魔術的でもこちらのほうが良いと考えた。

その2

matched = Array.new
base = Array.new
target = Array.new

gets.chomp.to_i.times { base << gets.chomp.delete(" ") }
gets.chomp.to_i.times { target << gets.chomp.delete(" ") }


target.each do |ts|
  matched << ( l = Hash.new )
  base.each_with_index do |x, i|
    o = -1
    while o = x.index(ts, (o + 1))
      l["#{i} #{o}"] = [i, o]
    end
  end
end

print(matched.first.keys.select do |k| 
  y, x = *matched.first[k]
  matched.length.times do |n|
    unless matched[n]["#{y + n} #{x}"]
      break nil
    end
  end
end.first)

ダメだと言われたので改修してみた。
各パターンにおいてマッチするメトリックをハッシュに格納し、マッチ側パターンの行数縦に連続するものをマッチとみなす。
速度的にも効率的にも悪くない。これで通過

水着

Paiza A級。

階乗の、末尾0を除いた末尾9桁を出力せよ、という問題。
本来はInteger Overflow対策が求められるのだろうが、RubyはBignumがあるため、そこは不要。

しかし、実効速度に伴うタイムアウトという壁が立ちふさがった。

その1

def fact(n)
    if n <= 1
        n
    else
        n * fact(n - 1)
    end
end

base_n = gets.chomp.to_i
ret_n = fact(base_n).to_s
ret_n =~ (/(.{0,8}[^0])0*$/)
print $1.sub(/^0*/, "")

理論上はOKで、実際テストではOKだったが、Case3でコケる。
おそらくはコールスタック枯渇。

その2

num = (1 .. gets.chomp.to_i).inject {|x, sum| x * sum }
print num.to_s.sub(/0*$/, "")[-9 .. -1]

コールスタックが枯渇しないように、Enumerable#injectを使ったループに変更した。
だが、Case4で15.00secで失敗する。おそらくはタイムアウト。

その3の1

print((1 .. gets.chomp.to_i).inject {|x, sum| (x * sum).to_s.sub(/0*$/, "").to_i % 1000000000 })

タイムアウトを避けるため、常に9桁に抑制することにした。

このコード、計算結果が1000000000だった場合に、商余が0になってしまうため失敗するが、事前に0を除いているために起こりえない。

だが、これはあっさり失敗してしまう。
手元では通過していたため、失敗理由がわからなかった。

その3の2(通過)

print((1 .. gets.chomp.to_i).inject {|x, sum| (x * sum).to_s.sub(/0*$/, "").to_i % 1000000000000 } % 1000000000)

計算桁を増やして最後に9桁に落とすようにした。
これで通過したが、計算桁はなんとなく増やしているだけなので、理論的には失敗しうる。

その3についての検証

r = (1 .. gets.to_i).inject([1, 1]) do |sum, x|
  puts "---" + x.to_s
  puts( a = (( x * sum[0] ).to_s.sub(/0*$/, "").to_i % 1000000000) )
  puts( b = (( x * sum[1] ).to_s.sub(/0*$/, "").to_i % 100000000000) )
  sum = [a, b]
end

puts "**ANSWER"
puts r[0]
puts r[1] % 1000000000

puts "*****"

p(137921024 * 50)
p(83137921024 * 50)
p((137921024 * 50).to_s.sub(/0*$/, "").to_i)
p((83137921024 * 50).to_s.sub(/0*$/, "").to_i)
p((137921024 * 50).to_s.sub(/0*$/, "").to_i % 1000000000)
p((83137921024 * 50).to_s.sub(/0*$/, "").to_i % 100000000000)

なんとなく想像はついたが、検証してみた。

!50の結果が変わってしまう。これは、!49の時点では

137921024
83137921024

と9桁/11桁で正しいが、!50になると

68960512
41568960512

と8桁/11桁になってしまう。0を除く前の時点で

6896051200
4156896051200

1桁増えているが、0が末尾に2つ増えており、0を除くことで2桁減る。結果として1桁減ってしまうことになる。

計算を続けていくと、この誤差は消滅するが(桁上がりすれば良い)、与えられた数によっては失敗することになる。
これを考慮すると正しくは

print((1 .. gets.chomp.to_i).inject {|x, sum| (x * sum).to_s.sub(/0*$/, "").to_i % (1000000000 * (10 ** ($&.length.zero? ? 1 : $&.length) ) ) } % 1000000000)

とすれば良いはず。

Paizaへの不満

条件が予測できない

条件は一応書かれてはいるが、それ以外の条件が多すぎる。

コールスタックの枯渇は予測できるからいいとしても、タイムアウトという条件は記載されていないし、めがねのその1が失敗する理由もわからない。

これで技術を評価されるというのはとても不満だし、だいたい大した機能もない開発環境で、妥当性検証の方法も単一のサンプル、それもどのような入力かを明確にしないままであり、入力の仕様も(業界では一般的な形式なのかもしれないが)非常にわかりにくい。

デバッグできない

失敗した場合でもなぜ失敗したのか分からない。そのためデバッグすることができない。

提出前に失敗した場合でも、全体出力を見ることができず、かつデバッグせずに提出することを前提としているためデバッグはかなり難しい。

私はこうしたことは不毛だと思っている。プログラムはちゃんと磨き上げて動くコードにするものだろう。そして間違いなく動くことが確認されて、はじめてリリースだ。

確認もデバッグもできないままということで、一度しか提出チャンスがないということにも強い違和感を覚える。

いつからこんなつまらない業界になったのか

これはPaizaに限った話ではないけれど、IT業界が、まず履歴書、そして業務履歴書なんてつまらない業界になってしまったのはいつからなんだろう。

少なくとも、私がお呼ばれした時には能力さえあればいいという空気があった。その時点でコンピュータを使うこなせる人間なんて変人が多かったし、型なしの掟破りという印象が強い。

だが、近年は、他業界よりもさらに「即戦力」ばかり求められ、経験がなければやらせないというところばかりで、「経験はどこでつめますか?」という状態。
結局そのために経歴の捏造が横行しているわけだ。

年齢も経歴も関係なしの実力次第の世界だと思うのに、そこまで経歴に固執するのは一体なぜなのだろう。

折角、コードを直接みることができるPaizaであるにも関わらず、そのフォーマットを要求するのはいかがなものか。さらに「趣味/1年未満」というくくりは一体なんなのか。
そして、定型的な業務におけるカテゴリしか用意していないのもなんなのか。実際はもっと多様で、能力とは直結していないはずなのだが。