愛と勇気と缶ビール

ふしぎとぼくらはなにをしたらよいか

「私の男」、あるいはまっすぐ飛んでこないボールをキャッチしていくこと

桜庭一樹の「私の男」を一晩で読み返し、次の日の午後に映画の「私の男」を観に行く、というオレオレ趣味満開な週末を過ごした。本来こういう時間の使い方をしたいからこそ働いているわけだが、中途半端な向上心や危機感のせいで普段はこういうことがあまり出来ない。哀れ、現代の個人。

で、結論から言うと映画は微妙だった。というか、桜庭一樹の文章によって喚起される俺の脳内「私の男」の圧倒的な情報量および解像度に2時間の映画では全然追いつかなかった、というのが正しいか。つまり俺も、ご多分に漏れず原作厨力を存分に発揮して「俺の思ってる『私の男』じゃない!ダメ!」という裁定を皆さんの前で下しているというわけなのだよ。

さて、映画の話はどうでも良いとして、原作の小説「私の男」は桜庭一樹の作品の中で僕が最も好むものだ。「砂糖菓子の弾丸は撃ち抜けない」や「赤朽葉家の伝説」も割と好きなのだが、「私の男」はブッチギリで一位に輝いている。

どうしてだが分からないが、この小説の第一章「花と、ふるいカメラ」を読むと涙がポロポロ出てきてしまうのだ。といっても、お涙頂戴なシーンが連続するわけでもないし、難病にかかったヒロインが世界の中心で死ぬわけでもない。ドライでウェットで少し異常な、養父と娘の別れがあるだけだ。

それは、二回目に読んだ時も変わらなかった。何が悲しいでもない、しかし涙が出てくる。内容には表れてこない、桜庭一樹の文章に込められた喪失感、取り戻せない何か、過去から続いてきたが今はもうそこには無いもの、それらが脳の切なさを司る部位に突き刺さってくるのだろう。

(ここで、暑苦しい夏の夜中に、文庫本を読んでベッドに寝そべりながら、涙を流す三十路間近のオタク男性を想像して頂きたい。お分かり…頂けただろうか?)

その涙腺のいささか緩んできた俺は、映画を見る前に、「私の男」というワードで下調べサーチ(?)をしたのさ。そしたら読書感想サイトみたいのが引っかかって、それを見て残念な気持ちになった。

どうして残念な気分になったか?を説明すると少し長くなるが、要は、「私の男」というのは父と娘の近親相姦がストーリーの主軸になっているんだわ。別にテーマとかそういうんじゃなくてね。

で、感想サイトに投稿している人のいくらかが、見事にこの近親相姦の部分に引っかかってしまっていたのだ。つまり、愛だとか何とか言っても「虐待」「洗脳」じゃないのこれ?と、現実世界の倫理や常識をもって、作品の中の人物を裁いてしまっている。

よし、一旦落ち着こう。まず、あらゆる人にはあらゆる感想を持つ権利がある。だから、上記のような感想を持つな、小説は現実を一旦カッコに入れてから読むもんだ、と言う気は全くない。どんな感想を持つかは個人の自由に委ねられている。が、同じ本を読むにしても、その本に込められた様々な意味と可能性を「縮退させてしまう読み」を行うよりは、逆にそれらをより豊穣にする読み方をした方が人生お得ですよ、ということだけは言えるだろう。

ここで言う「縮退させてしまう読み」とは、現実の自分の頭の中にある価値観・思想・宗教などを一旦忘れてしまわず、それらをそのまま小説の中に持ち込んで、判断の基準にしてしまうことだ。

もちろん、そうした現実の価値観を完全に捨てた上で本を読む、なんてことは人間には出来ないわけで、一時的に忘れたフリをする。忘れたフリをしているうちに、登場人物の持っている、あるいは作品全体に瀰漫している雰囲気・価値観が脳に侵食してきて、それにシンクロできるようになる。もちろん真面目に考えれば近親相姦は気色悪いし、実際に淳悟と花のような親子が近くに居たら僕も「それは体のいい虐待だ」という判断を下すかもしれないが、それとこれとは別のこと。小説にダイブするには、一旦そういうことは忘れた上で作者の紡いだ世界に入場しなければならない。

いやいや、別に「キモッ」と思って本を閉じたっていいんですよ、別に。ただ、勿体無いなーって思うだけでね。それだけの理由でこの小説を切ってしまうのは。

僕の好きな小説家、高橋源一郎先生は、ある時、こういったのさ。「小説を読むのはキャッチボールをするようなもんだ」って。

いや、キャッチボールじゃなくて普通にピッチャーとキャッチャーの話だったかな?大枠は合ってるから、どちらでもいいんだけどね。

小説を書く人は、こちらにボールを投げてくる。こっちのグローブめがけて、ゆっくり、取りやすい球を投げてくれる人もいれば、ものすごいスピードで、こっちが思い切って体を動かさないとキャッチできないような球を投げてくる人もいる。

初めは誰でも、自分の方に飛んでくる、とりやすいボールしかキャッチできない。色々なボールをキャッチしていくにつれ、速いボールや、とんでもない所に飛んでくるボールも、だんだん捕れるようになるんだ。

桜庭一樹先生は、普通とは少し外れた所に、まあまあのスピードでボールを投げた。さっと動かないと、取りこぼしてしまうかもしれないボールだ。でも、そのボールをちゃんと受け止められた時、その人が味わえる切なさ、なんとも言えない感じ、それは普通のボールよりも格別なんだ。

(ちなみに、高橋源一郎先生のデビュー作、「ジョン・レノン対火星人」は、結構きわどい線を転がるボールだ。キャッチに自信のある人は、試してみるといいかもしれない。)

だから、僕が言いたいのは、みんなもっと色んなボールを捕れるようになって、キャッチボールを楽しもうぜ!という、単にそれだけのことなんだ。

私の男 (文春文庫)

私の男 (文春文庫)

「私の男」はいいですよ。

MVNO(というかb-mobile)にMNPして6ヶ月目になった訳だが

http://zentoo.hatenablog.com/entry/2014/01/18/191458

だいたい↑の記事を書いた頃にNexus5を買ってb-mobileにMNPしたので、大体6ヶ月。それにしても上の記事、何の理由であんなにブクマがついたのかさっぱり分からん。iPhone派のオシャレなIT系の皆さん(?)の琴線(?)にでも触れたのだろうか。まあ、どうでもいいけど。

いわゆるdocomo, AU, softbankなどの御三家キャリア(そんな呼び方あるのか?)との契約からいわゆるMVNOに乗り変える時の懸念って、だいたい以下のようなものだと思うのだが

  1. 電波悪かったりしないのか?
  2. キャリアのサポート受けられないと辛いのでは?
  3. 安めのプランだと、通信量制限(と、それに絡んだ帯域制限)きつくない?

1は、そもそもMVNOって回線自体は各キャリアから借りる形になっているのでほとんど気にしなくていいレベルの迷信だと思っている。僕自身が使った実感としては、「電波良くもなければ、悪くもない」という感じ。ちなみにb-mobileの場合はdocomoの回線なので、ほぼdocomoのLTEに準じていると思われる。domocoのピュアな回線を別途持っていたりはしないので、真面目な比較は出来ない。あくまでも、感覚。ちなみに沖縄では電波が少なかったが、docomoが元々地方に弱いからなのかどうなのか、不明。旅行先でまでメールやらFacebookやらTwitterやらをそこまで見たくないので、個人的には丁度良かった。

2は、全く辛くない。そもそもsoftbankの時点で、何か困ってキャリアの問い合わせ窓口に行ったことって皆無だったし、iPhoneが物理的に壊れた場合でも結局その辺の怪しげな修理屋に行って直してもらっていたので、要はキャリアとの直接契約に特に意味がなかった。エンジニアの人は多少変なことがあっても調べて何とかなる場合がほとんどだと思うので、特にMVNOをオススメする。安いし。

3は、僕の今の契約だと月に3GBまででそれ以上が低速通信になる契約なのだけど、特に問題ない。家と会社で常時Wi-Fi繋いでるからってのもあるので、この辺は環境次第だろう。ここさえクリアーできれば回線代をケチれる所なので、興味のある人は毎月どれくらいデータ通信が必要か計測してみるとよいだろう。先月分の僕の統計は、以下のような感じだった。

f:id:zentoo:20140629221348p:plain

先月がだいたい1GBくらいしか使ってないので、僕の場合はもっとケチる余地があるというわけだ。

以前softbank + iPhoneだった時はだいたい月7000円くらい払っていて、今はだいたいその半分くらい。たかが月3500円、されど月3500円。

  • 無駄な固定費を避けたい
  • キャリアを捨てて、野に出る勇気がある

人にはとってもオススメな節約手段だと、今は思っている。

日本通信 bモバイル スマホ電話SIM フリーData マイクロSIM [AM-SDL-FDM]

日本通信 bモバイル スマホ電話SIM フリーData マイクロSIM [AM-SDL-FDM]

僕のGoogle PlayのDeveloper Accountがterminateされた件

いや、僕、別にそこまでヤヴァいアプリを出してたわけじゃないんですけど。

ただ、今まで来ていたメールを見るに、複数のアプリが「性的・暴力的なコンテンツ」とか「YouTubeの利用規約違反」とかに引っかかってGoogle Playから消された結果、その辺の情報を鑑みてアカウントごと停止になったというのが事実に近いのではないか。(ちなみにGoogle PlayのDeveloper Accountが停止になっただけで、いわゆるGoogleのログインアカウントの停止を食らったわけではない。)

前者は多分アプリに入っていた広告のコンテンツが問題で、後者は僕がYouTubeの利用規約を読んでいなかったのが悪いのだから、どちらについても処置に不服はない。僕が悪うございました。

ただし、これらのアプリを僕がGoogle Playという名の野に放ったのは2年以上前であり、既にマーケットに公開して2年以上以上経ったアプリが突然「定期的な審査」によって抹消されるというのは、あまり穏便ではない。穏便ではないというか、何らかの動きが感じられる、といった方が正確だろうか。

有り体に言うと、Google Playは名実ともにもうフロンティアではなくなった、ということだろう。つい数年前までは個人デベロッパーの作ったある意味クソアプリが跋扈する面白い空間だったのだが、すっかりソフィスティケートされたコンテンツの多いマーケットになってしまった。

まるで、iPhoneのApp Storeみたいだ。

ちなみにユーザから見れば、質の低いアプリも混ざった玉石混交なコンテンツマーケットというのは別に何も嬉しくはないわけで、僕の慨嘆は、何の力もない一個人デベロッパー、それも特に際立ったものを作り出すことの出来ない1デベロッパーによる「Google Playもつまんなくなっちゃったな」という負け犬の雄叫びにしか過ぎない。

ところで、僕ごときの作っていたライトグレー(?)なアプリすら放逐されるということは、他にも並み居るライトグレー、ミドルグレー、ダークグレーなアプリ群は今現在・今後もどんどんGoogle Playから駆逐されていっている・駆逐されていくと考えて間違いないだろう。やべえ。プラットフォーム、怖いわ。

残念なことに、僕は最近個人的に開発を検討していた、とても真っ当至極真っ白なアプリもGoogle Playに提出することが出来なくなってしまった。内容は、Bluetoothイヤホンの電池残量をツールバーアイコンに表示するというもの。

アーメン。結語はなし。

プログラマーは文系の仕事か、理系の仕事か

というような大きく構えたタイトルにしてみたが、デジタルな結論を持った記事ではない。

教育制度として文系とか理系とか分ける意味あんのか、というような議論はさておき、現行でそういう制度が存在している以上は僕の身の周りにも文系学部からプログラマーになった人、理系学部からプログラマーになった人がいて、僕の知る限りでは両者にプログラマーとしての能力の差は見受けられない。

世間では、どうやらプログラムを書くのには数学的な能力が必要だと思われているせいか、あるいはいわゆる情報システム系の学部が理系学部に分類されているせいか、理由は全員に聞いてみたことがあるわけじゃないのでよく分からないが、どうやらプログラマーといえば理系だと思っている人が多いようだ。

僕個人で言うと、大学・大学院と数学の点数の低さを英語の点数でカバーしてきた(これは実際には点数の照会なんてしてないので実際には不明なのだが、明らかに手応えとして英語の方が出来ていたのでこういうことにしている)こと、かつ複数行の数式を繰り返し見ると発狂することなどからいって、自分はどっちかというと文系脳なのだろうと思っている。文系脳という言葉が曖昧なら、能力が言語能力の方に寄っている。その人間がまがりなりにもコードを書いて生計を立てているので、やはり文系理系というのはあまりプログラマーとしての能力に関係がないような気がしている。

とまあ、曖昧な分類の上で人々および個人の曖昧な認識を語るのはあまり意味がないとしても、人間の頭の中の曖昧な分類、曖昧な認識はその人の行動に影響を及ぼす。これは間違いのないところである。

要は何がいいたいかというと、「コードを書く」という営為を「数学的なモデルをコードに落とす」と捉えるか、「誰かに伝わるように文章を書く」と捉えるかで、その人のコードの書き方は影響を受けるのではないか、ということである。

あえて両極端な例を挙げたが、実際にコードを書いた人なら誰でも、コードを書くという営為が(極端な例を除いて)どちらか一方に還元できるものではないことを知っている。コードは数学的なモデルを実現するものでもないし、誰かへの手紙でもない。数学的に正しくても現実界に意味のある影響を与えなければ用をなさないし、読みやすければ効率が悪くてもいいかといえばそうでもない。

汚いコードよりは綺麗なコードが良いが、効率を追求するためには読みやすさを犠牲にしなければいけないこともある。コンパイラがエラーを吐かなくても、人間が読めなければ保守できない。同じ結果をもたらすコードでも、人的ミスを誘発しやすいコードもあれば、そうでないものもある。これら全てはバランスであって、教条的な判断(例えば、メソッドはとにかく短ければ短いほどいいとか)を積み重ねるだけではどうにもならないものである。正しい唯一のコードの書き方なんてものがないのは、正しい唯一の文章なんてものがないのと同じである。同じ目的を達成するにも、千差万別の書き方がある。

微妙に脱線したが、同じコードを書くといっても人ごとに頭の中のものをコードに落とす時のやり方が違うので、コーディングというのは、非常に面白く、また人間的な営為だなあ、と僕は思っているのである。ここまでの流れだと、イマイチ伝わらないかと思うが。

さらに脱線すると、関数型は果たしてオブジェクト指向の次のパラダイムかどうか云々、みたいな議論がたまにあるわけだが、僕は、オブジェクト指向というものが人口に膾炙した理由はそれがプログラミングの技法として適切であるからとかそれを使うとコードの設計がうまくいくとかそういったことが本当の原因なのではなくて、オブジェクトのない世界観(f -> g -> hみたいな関数の連鎖)よりも、オブジェクトのある世界観、つまり「AがBする」「BはCを持つ」みたいな世界観の方が人間の世界の捉え方(もっと大雑把に言ってしまうなら、人間の脳)にマッチしているからなのではないかと思っている。(断っておくがこれは実世界のモデリングが云々とかそういう話とは全然関係がない)要は正しさの問題ではないのである。

(ちなみに、この「オブジェクト指向は人間が認識しやすいのだ」理論は会社の後輩のHaskellerも同じことを言っていた。だから何、という話ではない)

もう一度脱線すると、最近会社でとあるコードを見ていて、「このコード書いた人、迷いがあるな」と思って喋ったら、なぜそのタスクをやるか、最終的な形はどうあるべきか、みたいなことがその人の中ではっきりしていなかった、ということがあった。コードに迷いがあるといっても、コミット数が変に多いとか、表記にゆれがあるとか、動作上問題があるとか、コメントに // TODO // ここどうしたらいいかな? と書いてあるとかそうしたことではなくて、何かよろしくないオーラが出ていた。とりあえず間に合わせようとしている感じ、というか。 僕も30に近づいて、多少エスパーになったのかも知れない。

人間のやることなので、そこに人間が滲み出してくるのは当たり前といえば当たり前なのだが、なんとなく一般通念に反論してみたくて書きました。

2014年にもなってWebページをまともに印刷する方法も知らない俺たちは(あるいは、とあるウェッブエンジニアの闘いの記録)

前略、window.printという関数をJavaScriptから呼び出せば、めでたく印刷ダイアログが表示されて、今目の前に表示されているウェッブページを印刷することが出来るわけだ。

が、とりあえずこの機能をそのまま使うとおかしい。何やら色々おかしい。media="print"なスタイルシートで指定したbackground-colorすら反映されない。「ブラウザの印刷オプションで出来る」という噂もあるが、僕の環境ではちゃんと色が出なかった。

これは一体どうなっているんだ?2014年にもなって、俺達はウェッブページを、自分がいま目の前にしているそのままの姿で印刷することすら出来ないのか?HTML5技術のカンファレンスを開いたり、WebSocketでチャットを作ったりしてる場合じゃない!事件はカンファレンスで起きているんじゃない、俺の家のプリンターの前で起きているんだ!

ゲンよ、画像じゃ。画像を使うんじゃ

というわけで僕はアッと驚くワークアラウンドを探すための旅に出たわけだが、何となくまともな印刷機能を提供していそうなGoogleという会社の、Googleカレンダーというサービスについてさらっと調べてみた。

どうやら、Googleカレンダーは「今見ているウェッブページ」をそのまま印刷するのではなく、一旦画像を生成して、その画像を表示したページを印刷することで回避しているようだ。コードをチラ見したところ、"Print"ボタンを押した際にiframeでアレコレしている節もある。

よし、これだ!画像は印刷でそのまま色が載るのだ。画像なら、スタイルシートがどうのこうのと悩むこともない。我が社もこれで行こう。

f:id:zentoo:20140211154855p:plain

画像をどうやって作るか?

で、だ。画像はどうやって作る?Googleはおそらくカレンダーのデータを画像としてレンダリングするような内部APIを持っているのだろう。これは間違いなくクロスブラウザなソリューションだ。が、自分でそんなものを用意するのは面倒くさくて仕方がない。

要は、今見ているページのDOMを、何らかの形でimgのsrcにぶっ込めるURLに変換できればいいわけだ。よし、ちょっと迂遠だがcanvasでいこう。

html2canvasというライブラリがある。試した所、ある程度まともに動きそうだ。これを使おう。

html2canvasを使って、DOM -> canvas -> Data URIという変換を行う

html2canvasを使うと、以下のような感じでいけるのではないか。

  1. html2canvasで、現在のページのDOM(またはその一部)をcanvas elementに変換する
  2. canvas elementのtoDataURL()でData URIを得る
  3. same originかつ不可視なiframeを開き(または予め開いておき)、その中のimage elementのsrcに親frameから先ほどのData URIをぶっ込む
  4. iframe.contentWindow.print()を呼び出す

完璧じゃないか、我が軍は。

実際のコード

なんとなく、Chrome 33.0.1750.70 betaで動くことは確認した。

    function printWithCanvas() {
        html2canvas(document.getElementsByTagName("main")[0], {
            onrendered: function(canvas) {
                var iframe = document.getElementById("print-iframe");
                var iframeWindow = iframe.contentWindow;
                var imageElement = iframeWindow.document.getElementById("canvas-image");

                if ( typeof canvas.toBlob === "function" ) { 
                    var blobURL = URL.createObjectURL(canvas.toBlob());
                    imageElement.src = blobURL;
                }   
                else {
                    var dataURL = canvas.toDataURL();
                    imageElement.src = dataURL;
                }   

                iframeWindow.print();
            }   
        }); 
    };  

iframeの中味は、以下のような感じのHTMLにしておく。

<!DOCTYPE HTML>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title></title>
    <style type="text/css">
        #canvas-image {
            width: 100%;
            height: 100%;
        }   
    </style>
    <script type="text/javascript">
    </script>
</head>
<body>
    <img id="canvas-image" src="" alt="" />
</body>
</html>

HTMLCanvasElementのtoBlobがある場合はそれを使う。比較的良心的ブラウザーであるところのFirefoxには既に搭載されているようだ。canvasをimageとして扱う際にData URIにわざわざシリアライズしなくてはいけないのはとっても非効率なので、このAPIはとっととcanvasの載っている全てのブラウザーで実装されてほしい。

canvasがまともに動かないレガシーなブラウザでは、uuCanvasを使えばよいだろう。まだ、試していないが...

まとめ

  • window.printでそのままページを印刷するのは厳しい
  • html2canvasはそれなりに動く
  • ブラウザーはすべからくHTMLCanvasElementのtoBlobを実装すべし

今回は駆け足で考えかつ実装しましたが、もっといい方法があれば教えて頂きたいです。

Martiniでtemplateのデリミタがclient sideのそれと被るのを回避する

最近、「個人プロジェクトだし、別にどの言語で書いてもいいや」というようなものについては試しにgolangで書いてみることが多くなった。単なる素振り。

で、それがWebアプリである時は codegangsta/martini · GitHub を使うわけだが、これに限らずgolangのtemplateではmustache的なアレ ( {{ }} のこと ) がデリミタであるのが基本である。

client sideのtemplateが同様のデリミタを採用していると爆死するので、このように回避する。

    m := martini.Classic()
    m.Use(render.Renderer(render.Options{
        Layout: "layout",
        Delims: render.Delims{"[[", "]]"},
    })) 

martiniを使ってない場合、特に組み込みのtemplateをそのまま使っている場合は、まあドキュメントでも自分で読んでくれい。

Google accountでAuthenticationかましつつAuthorizationもかましたい場合

ハーイ。今日のお兄さんは優しくないので、タイトルの2つを混同してるやつ死刑ね。

Google Accounts Authentication and Authorization — Google Developers

とか

Using OAuth 2.0 for Login (OpenID Connect) - Google Accounts Authentication and Authorization — Google Developers

とかを見てると、「例えばJavaScriptからAPI叩きつつ、裏側でGoogle+ Platform — Google Developers使ってユーザ認証もしておきたい場合はどげんしたらいいの?どれをどう使えばいいの?一体どっちなの?」という疑問に捉われる。

正解はgoogle plus loginの方を使いつつ、gapi.client相当も読み込んでおいて

    <script type="text/javascript">
      (function() {
        var po = document.createElement('script'); po.type = 'text/javascript'; po.async = true;
        po.src = 'https://apis.google.com/js/client:plusone.js?onload=onLoadCallback';
        var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(po, s);
      })();
    </script>

API叩く用のscopeも指定しつつAuthorization Request送って(以下はGoogle+ sign-in buttonを使う場合)

    <span id="sign-in">
        <span
            class="g-signin"
            data-callback="signinCallback"
            data-clientid="[YOUR CLIENT ID]"
            data-cookiepolicy="single_host_origin"
            data-scope="openid https://www.googleapis.com/auth/calendar.readonly"
        >
        </span>
    </span>

後は普通にJSからAPIを叩けばオケー。これ以降のユーザ認証についてはドキュメントに書いてある通りなので省略。

        function signinCallback(authResult) {
            console.log(authResult);
            if ( authResult.status.signed_in ) {
                gapi.client.load("calendar", "v3", processCalendar);
            }
            else {
                document.getElementById("sign-in").style.cssText += "display:inline;";
            }
        }

        function processCalendar() {
            gapi.client.calendar.calendarList.list().execute(function(res) {
                console.log(res);
            });
        }