Windows PerlでUnicodeなファイル名を使ってはまる

業務でPerlでプログラミングしていたのだが、 Unicodeなファイル名で出力していたら文字化けする、という症状が報告された。

ミニマムのコードとしては以下の通り

use utf8;

open(FH, ">", "てすとふぁいる");
FH->print("テスト出力");
close(FH);

これはLinuxでは問題なく動作するし、それについては実際に動作させて確認もしてある。 ところがWindowsではこれが文字化けする。

まぁ、NTFSはUTF-16LEだもんね。 というわけで対応してみる。

use utf8;
use encode;

open(FH, ">", encode("UTF-16LE", "てすとふぁいる");
FH->print("テスト出力");
close(FH);

ところがこれでもだめである。

もちろん、次のようにすれば動作するのは知っている。

use utf8;
use encode;

open(FH, ">", encode("cp932", "てすとふぁいる");
FH->print("テスト出力");
close(FH);

おそらくWindowsがShift-JISをシステムエンコーディングとして使っていて、FATの日本語ファイルにもShift-JISを使っていたこととの互換性のためにシステムが何かをしているのだろう。 だが、その何かの結果うまくいかなくなってしまう。 日本語ファイルならShift-JISで書くという手もあるが、今回の場合お客様の要望としてはファイル名にはハングルを使う。しかしシステムは日本語Windowsである。

さて困った。

Win32::Unicodeという手もあるようだが、これがUnicodeでかけるのかは結構疑問だ。 さらに言えば、今回の場合かなりの数のファイルを書くため、速度低下が無視できない。

ちなみに、Ruby (RubyInstaller)だと

File.open("てすとふぁいる", "w") {|f| f.puts "テスト出力" }

ちゃんと動作したりする。

テストしたところ、Active Perlでも、Strawberry Perlでも問題は同じ。 しかしCygwin Perlだと起きない。 Cygwinを使うというのは初心者にとってちょっとハードルが高いので避けたい部分ではあるのだが…

なお、RubyはMinGWを使っているから大丈夫なのかもしれない。

お仕事ではPerlも使えます。 今じゃ使える人も少ないので貴重なのではないかと思う。 ご依頼、お待ちしてます!!

幼少プログラマは大卒プログラマに追い越される運命にあるのは本当か

Twitterでこんな発言が軽くバズった。

Togetter

2歳からプログラミングを始めた幼少の転載で、今はそうでもない私が反論しよう。

基本的にプログラミングというのは、素質の問題と、経験の問題がある。
幼少からピアノを習っている子は音楽の授業なんてりんごが食べ物であると習うくらい退屈なことだが、普通の子にとっては旋律的短調だの弱起だの言われても呪文でしかない。
大学の情報科でも学生のレベルは天から地まで様々だ。

私には似た境遇の友人がいた。
私は勉学以外を求めて音楽家になり、彼は勉学を求めて一流大の情報科に行った。
私達は大学の勉強は私達のレベルには届かないと考えていたし、事実彼は大学での学びはひたすらに消化し、自身の努力でレベルを上げた。

私はコンピュータに関する努力を怠ったが、大学卒業時点ではまだ私が優位だった。

そもそもプログラミングの世界では、教わることでできるようになるレベルはまだスタート地点にも立っていない。それはアビバの生徒レベルだ。
だから、独学か、誰かに教わっているかということは瑣末なことで、その気になれば1年で埋まる話でしかない。というより、教わらなければ学べない人は、多分先は知れている。好奇心で自分から動かないプログラマなんて、雑魚だからだ。

ただし、環境が許さず関心も失って一度はドロップアウトした私のように、努力を続けることは当たり前に差になる。慢心し、努力を放棄すればやがては脱落するのはどの世界でも同じだ。

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

前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;には対応しておらず、&が含まれるためクローンされてしまう

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

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

原文に言及

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() {
  / *** /
}

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

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年未満」というくくりは一体なんなのか。
そして、定型的な業務におけるカテゴリしか用意していないのもなんなのか。実際はもっと多様で、能力とは直結していないはずなのだが。

Pureminder (リマインダ)とZsh socket programming

Tasqueにはリマインダ機能がない。

Taskcoachということも考えはするが、残念ながらTaskcoachは私の環境ではSystem trayに収まってくれないため、非常に邪魔である。

そこで、リマインダを作ってみた。GitHubで公開している。今回はどちらかというとNoddy寄りだが、きちんとした形で公開している。

PureminderはZshで書かれている、クライアント/サーバーである。
サーバーはメッセージを受け取り、SOX(play)で音を再生し、Zenityで画面に表示する。

わざわざクライアントサーバーモデルをとっているのは、atでの利用に問題があるためだ。

単にatでcallすると、Zenityは表示すべきディスプレイサーバーを見つけられない。
$DISPLAY変数によって指定することはできるが、マルチユーザー環境での動作が信用できない。

そこで、確実に機能させるため、現在のデスクトップ上で起動し、ユーザー別に作られるUNIXドメインソケットでメッセージを受け取る、という仕様とした。

だが、PureminderはZshだ。
一般にはなかなか見かけることのない、Zsh socket programmingをちょっと紹介しよう。

まず、モジュールをロードする。

zmodload zsh/net/socket

次にzsocket -lでソケットを作成する。

zsocket -l "$sockfile"

$REPLYにファイルデスクリプタ(Integer)が入るので、とっておく。

typeset -g sock=$REPLY

zsocket -aで接続を待ち受ける。引数はソケットのファイルデスクリプタ。

zsocket -va $sock

$REPLYに接続のファイルデスクリプタが入る。この接続は全二重。
閉じる時は次のようにする。

exec {fd}>& -

Rubyのサブクラス内のスーパークラスのネストされたクラス

意味が分からないと思うが、ちょっとした疑問だ。

class A
  class B
  end
end

Class AA < A
  B
end

このコードではAAの中のBA::Bになる。 つまり、AAのコンテキストの中でBは使用可能だ。

class A
  class B
    def hi
      puts "Hi"
    end
  end
end

class AA < A
  class B
    def hihi
      puts "HiHi"
    end
  end
  
  def initialize
    @b = B.new
  end
  
  def b
    @b.hi
    @b.hihi
  end
end

aa = AA.new
aa.b

このコードでは

class AA
  class B
  end
end

AA::Bが作られ、@b#hiがないためエラーとなる。

class A
  class B
    def hi
      puts "Hi"
    end
  end
end

class AA < A
  class B < B
    def hihi
      puts "HiHi"
    end
  end
  
  def initialize
    @b = B.new
  end
  
  def b
    @b.hi
    @b.hihi
  end
end

aa = AA.new
aa.b

この場合は、

class B < B

によって、A::BをスーパークラスとするサブクラスAA::Bが作られる。

この定義によってBという名前がオーバーライドされることとなる。

class A
  class B
    def hi
      puts "Hi"
    end
  end
end

class AA < A
  B = B
  class B
    def hihi
      puts "HiHi"
    end
  end
  
  def initialize
    @b = B.new
  end
  
  def b
    @b.hi
    @b.hihi
  end
end

aa = AA.new
aa.b

これが本来意図するところだ。 オープンクラスを用いてスーパークラス内で定義されたクラスを拡張したいのだろう。

そこで

B = B

によって

AA::B = A::B

とした上でクラスを開けば良いのだ。

だが、今回の場合はPureDocのために実験した。PureDocではサブクラス内での#instance_evalによって評価された時に呼ばれるメソッドが名前でこのクラスのインスタンスを生成するため、あくまでもサブクラス(AA相当)の中に閉じ込められたクラス(B)でしかない。

ということは、そのクラスは

AA::B = A::B

ではなく

AA::B < A::B

であることが本来望ましいのではないだろうか。