2010-09-25

Ruby で書いたフィルタを Python で書き直す #1

以前、「単純なフィルタをつなげて複雑なフィルタを実現する」で書いたプログラムを Python で書き直してみよう。

Ruby で書いたフィルタ

シンプルなパイプライン

このパイプラインを実現するために Ruby で書いたフィルタは以下の 4 つ。

  1. all (all_rss.rb)
  2. titles (titles_rss.rb)
  3. mklist (mklist.rb)
  4. mkhtml5 (mkhtml5.rb)

今回は、この内、titles_rss.rb を除く、3 つのフィルタを Python で書き直した。titles_rss.rb を除いたのは、このフィルタではフィードの XML データをパースする必要があるから。Python (2.5) の標準ライブラリにはフィードの XML を扱うライブラリが(RSS、Atom のどちらも)ふくまれていないから。外部のライブラリを使うにしろ、自前で簡単なパースをするにしろ、他のフィルタの書き直しよりは手間がかかるので後回しにした。

Python による実装

以下に示すコードを見ればわかるが、いずれも Ruby のコードとほぼ 1 対 1 で対応が付く。

フィードを取得する (all)
(all_atom.py)
 1: #!/usr/bin/python
 2: # -*- coding: utf-8 -*-
 3: # all_atom.py: get all feeds in the Atom format.
 4: 
 5: from sys import stdin, stdout
 6: import urllib
 7: import re
 8: 
 9: BLOGID = "put your blog id here"
10: FEEDURL = "http://www.blogger.com/feeds/%s/posts/default" % BLOGID
11: 
12: def get_atom(max_results):
13:     params = urllib.urlencode({'max-results': max_results})
14:     f = urllib.urlopen(FEEDURL + "?%s" % params)
15:     res = f.read()
16:     f.close()
17:     return res
18: 
19: # get meta information only
20: md = re.search('<openSearch:totalResults>(\d+)<\/openSearch:totalResults>', get_atom(0))
21: total_posts = int(md.group(1))
22: 
23: stdout.write(get_atom(total_posts) + '\n')
リスト形式に変える (mklist)
(mklist.py)
 1: #!/usr/bin/python
 2: # -*- coding: utf-8 -*-
 3: # mklist.py: make a HTML fragment contains an ordered list.
 4: 
 5: # SPECIFICATION:
 6: # input data must be written in the following format.
 7: # (<title> . <uri>)
 8: 
 9: from sys import stdin, stdout
10: import re
11: 
12: stdout.write('<ol>\n')
13: 
14: regex = re.compile('^\(\"(.*)\"\s.\s\"(.*)\"\)$')
15: for pair in stdin:
16:     md = regex.search(pair)
17:     if md:
18:         stdout.write("<li><a href='" + md.group(2) + "'>" + md.group(1) + "</a></li>\n")
19: 
20: stdout.write('</ol>\n')
HTML として出力する
(mkhtml5.py)
 1: #!/usr/bin/python
 2: # -*- coding: utf-8 -*-
 3: # mkhtml5.py: put input data into a well-formed HTML template.
 4: 
 5: from sys import stdin, stdout
 6: 
 7: stdout.write("""<!DOCTYPE HTML>
 8: <html lang='ja'>
 9: <head>
10: <meta content='text/html; charset=UTF-8' http-equiv='Content-Type'/>
11: <title>Blog Posts</title>
12: </head>
13: <body>
14: """)
15: 
16: for line in stdin:
17:     stdout.write(line)
18: 
19: stdout.write("</body></html>\n")

残るはフィードのパース

次回は、Atom 形式のフィードデータをパースし、記事のタイトルと URL を抽出する部分を書き直そう。実際のフィードデータを見てみると、単に rel 属性が alternate になっている link を抽出すればすみそうなんだが、いまやっていることは Python の練習でもあるんだから、もう少しまじめにパースするコードを書いてみるつもりだ。

関連リンク

関連記事

2010-09-24

パイプとフィルタで複数のデータの流れを扱うには?

前回示したパイプラインは単一のデータの流れを持つシンプルなフィルタをつなげたものだった。本来の意味でのフィルタはこういうものだ。今回はこれを拡張して、最終結果の HTML を複数のファイルに分割するプログラムを作る。各ページにはブログ記事のタイトル(とリンク)が一定数ふくまれる。

今回のプログラムは、一応「パイプとフィルタ」の形をしているが、その範疇に収まらない構造になっている。なぜ、そうなるのか? それは、プログラムに対する要求がシンプルなパイプラインで実現できる範囲を超えているからだ。最終結果が複数の HTML ファイルである以上、標準出力だけでは作れない。

処理の分割

前回と同様に、まずは全体の処理を複数のステップに分割することから始めよう。

  1. フィードを取得する
  2. 特定の要素(タイトルと URL)を抽出する
  3. データを分割する
  4. 分割されたそれぞれをリスト形式(HTML の OL)に変える
  5. 分割されたそれぞれを HTML として出力する

大きな違いは 3. 追加されたこと。さらに、4. および 5. は処理自体は前回の 3.、4. と同じだが、今回は分割されたデータごとに処理を行う必要がある。

分割されたデータを標準入出力でつながったメインのパイプに流すことはできない。それとは別のデータの流れを用意しなければならない。今回は、単純に中間ファイルを作ることにする。また、中間ファイルの名前をメインのパイプ(標準入出力)に流す。こうすることで、後のステップを担当するフィルタプログラムは、分割の詳細(何個に分けられたのか、それぞれのファイル名は何か)を気にしなくても良くなる。

つまり「データを分割する」は、より詳しく書くと「データを分割し、それぞれをファイルに書き出し、その名前を標準出力に書く」となる。また、分割後のステップは「標準入力からファイル名を受け取り、そのデータに処理を加え、(新しい)ファイルとして書き出し、その名前を標準出力に書く」になる。

以上を踏まえると、今回のプログラムを構成するパイプラインは以下のようになる。

データフローが多重化されたパイプライン

最後の rename は、中間ファイルの命名規則で書き出された HTML を、最終結果の名前にリネームするだけのプログラムだ。このステップを設けることで、リスト化と HTML 化に共通の制御フィルタ(後述)を使うことが可能になる。

Ruby による実装

alltitles は前回の all_rss.rbtitles_rss.rb をそのまま使う。

データを分割する
(split.rb)
 1: #!/opt/local/bin/ruby1.9
 2: # -*- coding: utf-8 -*-
 3: # split.rb: split a long list into pieces.
 4: 
 5: PAGESIZE = 25
 6: 
 7: page = 0
 8: start_index = 0
 9: lines = STDIN.readlines
10: length = lines.length
11: 
12: while start_index < length
13:   name = "page_#{page}.dat"
14: 
15:   File.open(name, "w") { |file|
16:     lines[start_index, PAGESIZE].each { |line|
17:       file.puts line
18:     }
19:   }
20: 
21:   STDOUT.puts name
22:   page += 1
23:   start_index += PAGESIZE
24: end

標準入力から行を読み込み、PAGESIZE で設定した行数ごとに中間ファイルに書き出している。ちなみに、ここはちょっと手抜きで、「まとめて読んで」から処理するロジックになっている。

シンプルなフィルタを適用する制御フィルタ
(apply.rb)
 1: #!/opt/local/bin/ruby1.9 -w
 2: # -*- coding: utf-8 -*-
 3: # apply.rb: apply a given filter to specified files.
 4: 
 5: PROCESS_FILTER = ARGV.shift
 6: basename = File.basename(PROCESS_FILTER, ".*")
 7: page = 0
 8: 
 9: STDIN.each { |name|
10:   resultfile = "#{basename}_#{page}.dat"
11:   system("#{PROCESS_FILTER} < #{name.chomp} > #{resultfile}")
12:   STDOUT.puts resultfile
13:   page += 1
14: }

これが、リスト化と HTML 化で使う「制御フィルタ」だ。データに対する処理を担当する「処理フィルタ」は引数として(フルパスを)受け取る。標準入力から読み取るのは(split.rb または apply.rb 自身が作った)中間ファイルの名前だ。「処理フィルタ」の呼び出しは system 関数でコマンドとして呼び出している。

最終結果を収めたファイルをリネームする
(rename.rb)
 1: #!/opt/local/bin/ruby1.9 -w
 2: # -*- coding: utf-8 -*-
 3: # rename.rb: read filenames from STDIN, then rename each files to
 4: # appropriate name.
 5: 
 6: page = 0
 7: 
 8: STDIN.each { |tmpname|
 9:   newname = "page_#{page}.html"
10:   File.rename(tmpname.chomp, newname)
11:   STDOUT.puts newname
12:   page += 1
13: }

標準入力から読み取った名前を、page_*.html の形にリネームしているだけだ。

すべてをつなげる

前回は、実行例は示さなかったが、今回は長くなったのと引数の指定が必要になったので、以下に示しておく。

[imac] mnbi% ./all_rss.rb | ./titles_rss.rb | ./split.rb | ./apply.rb ./mklist.rb | ./apply.rb ./mkhtml5.rb | ./rename.rb

実行結果は page_{0..9}.html という名前で生成される。

多重データストリームを処理するパイプライン

冒頭に書いたように、今回のプログラムは Unix 由来の「パイプとフィルタ」とは呼べない構造になっている。もし、多重データストリームを扱うことができるようなパイプラインを構築することができるなら中間ファイルを使う必要もなくなり、「パイプとフィルタ」と呼べるようになるだろう。しかし、それをシェルのレベルで実現することはやはり難しい。やはり、このあたりが「小さなプログラムをシェルで組み合わせて複雑な処理を実現する」という方法の限界だろう。

今回のプログラムは、いわばコマンドライン版の「ブログ記事一覧を作成する」プログラムだ。ウェブアプリとして作るなら、リクエストごとに作るレスポンスは 1 つだから、最終結果が複数のファイルになることはない(将来のリクエストを見越してあらかじめ他のページを生成しておくなら別だが)。ウェブアプリ版としては、最終結果を標準出力に書き出す前回のものの方が完成した姿に近いと言える。

関連記事

2010-09-23

単純なフィルタをつなげて複雑なフィルタを実現する

フィルタとして実現したプログラムは、解こうとする問題を、より小さな(そしてより簡単な)複数の問題に分割して扱うことを可能にする。また、フィルタをパイプでつなげたパイプラインもやはりフィルタとして扱うことができる。これは、フィルタプログラムを、より小さな(そしてより簡単な)複数のフィルタプログラムに分割して実装できることを意味する。部分と全体を同一視が可能。つまり、「パイプとフィルタ」は「Composite パターン」になっているのだ。

以上を踏まえて、「Blogger で作ったブログの記事一覧を作成する」で書いた「かけら」に戻ってみよう。今回の課題は、これをより小さなフィルタをつなげたパイプラインとして実現することだ。

処理の分割

get_titles.rb は Blogger へのフィードのリクエストから、HTML 化までを 1 つのプログラムで処理するものだった。これを処理の流れに沿って分割するとこうなる。

  1. フィードを取得する
  2. 特定の要素(タイトルと URL)を抽出する
  3. リスト形式(HTML の OL)に変える
  4. HTML として出力する

それぞれを独立したフィルタプログラムとして書きパイプでつなげれば、get_titles.rb と同等の処理が実現できる。

シンプルなパイプライン

Ruby による実装

それぞれのプログラムを見ていこう。

フィードを取得する
(all_rss.rb)
 1: #!/opt/local/bin/ruby1.9 -w
 2: # -*- coding: utf-8 -*-
 3: # all_rss.rb: get all feeds in the RSS format.
 4: 
 5: require 'net/http'
 6: require 'uri'
 7: 
 8: BLOGID = "put your blog id here"
 9: 
10: def get_rss(max_results = 0)
11:   url = URI.parse('http://www.blogger.com/')
12:   res = Net::HTTP.start(url.host, url.port) { |http|
13:     http.get("/feeds/#{BLOGID}/posts/default?alt=rss&max-results=#{max_results}")
14:   }
15:   res.body
16: end
17: 
18: # get meta information only
19: /<openSearch:totalResults>(\d+)<\/openSearch:totalResults>/ =~ get_rss(0)
20: total_posts = Regexp.last_match[1].to_i
21: 
22: STDOUT.puts get_rss(total_posts)  

get_titles.rb との違いは、全記事数を取得するために、max-results=0 のリクエストを投げていること。さらに、そうして取得した全記事数をもとに、一度のリクエストで全ての記事を取得していること、だ。記事が何千、何万とあるなら別だが、たかだか 200 や 300 なら一度のリクエストでまとめて取得した方が効率が良い。最終結果の HTML を分割するなら、それはプログラムの中でやれば良いことだ。

特定の要素を抽出する
(titles_rss.rb)
 1: #!/opt/local/bin/ruby1.9 -w
 2: # -*- coding: utf-8 -*-
 3: # titles_rss.rb: get post tiltes from Blogger RSS feed.
 4: 
 5: require 'rss'
 6: 
 7: rss = RSS::Parser.parse(STDIN.readlines.join, false)
 8: rss.channel.items.each { |item|
 9:   STDOUT.puts "(\"#{item.title}\" . \"#{item.link}\")"
10: }

入力は、RSS 形式のフィード(XML のテキスト)。RSS として読み取り、記事のタイトルと URL だけを抽出している。

get_titles.rb との違うのは、出力の形式。Lisp の cons ペアとしても読み取れる形式にしてある。

リスト形式に変える
(mklist.rb)
 1: #!/opt/local/bin/ruby1.9 -w
 2: # -*- coding: utf-8 -*-
 3: # mklist.rb: make a HTML fragment contains ordered list.
 4: 
 5: # SPECIFICATION:
 6: # input data must be written in the following format.
 7: # (<title> . <uri>)
 8: 
 9: STDOUT.print <<LISTSTART
10: <ol>
11: LISTSTART
12: 
13: STDIN.each { |pair|
14:   if /^\(\"(.*)\"\s.\s\"(.*)\"\)$/ =~ pair
15:     md = Regexp.last_match
16:     STDOUT.puts "<li><a href='#{md[2]}'>#{md[1]}</a></li>"
17:   end
18: }
19: 
20: STDOUT.print <<LISTEND
21: </ol>
22: LISTEND

入力としてわたってくるのは、2 つの文字列が Lisp の cons ペア形式で書かれたもの。取り出すのには正規表現を使っている。

ここで、ちょっと寄り道をしてみる。この段階だけを Gauche (Scheme) で書くとどうなるか?

(mklist.scm)
 1: #!/opt/local/bin/gosh
 2: ; -*- coding: utf-8 -*-
 3: 
 4: ;;; mklist.scm: make a HTML fragment contains ordered list.
 5: 
 6: ;; SPECIFICATION:
 7: ;; input data must be written in the following format.
 8: ;; (<title> . <url>)
 9: 
10: (define (apply-filter proc port)
11:   (let ((s-exp (read port)))
12:     (if (not (eof-object? s-exp))
13:  [begin
14:    (proc s-exp)
15:    (apply-filter proc port)])))
16: 
17: ;; BEGIN
18: (display "<ol>\n" (standard-output-port))
19: 
20: ;; MAIN
21: (apply-filter
22:  (lambda (s-exp)
23:    (let ((title (car s-exp))
24:   (url (cdr s-exp)))
25:      (display #`"<li><a href=',|url|'>,|title|</a></li>\n"
26:        (standard-output-port))))
27:  (standard-input-port))
28: 
29: ;; END
30: (display "</ol>\n" (standard-output-port))

プログラムの骨格は「Gauche でフィルタを書く」で書いたものと共通だ。

↑に示した Ruby 版との違いは、読み取るデータの形式が Lisp のデータそのものになっているため正規表現によるパターンマッチが必要ない、という点だ。データの形式によっては特定の言語の方がすっきりと書けることもある。その実例だ、と言うにはちょっと恣意的なサンプルだけど。

HTML として出力する
(mkhtml5.rb)
 1: #!/opt/local/bin/ruby1.9 -w
 2: # -*- coding: utf-8 -*-
 3: # mkhtml.rb: put input data into a well formed HTML template.
 4: 
 5: print <<HEADER
 6: <!DOCTYPE HTML>
 7: <html lang='ja'>
 8: <head>
 9: <meta content='text/html; charset=UTF-8' http-equiv='Content-Type'/>
10: <title>Blog Posts</title>
11: </head>
12: <body>
13: HEADER
14: 
15: STDIN.each { |line|
16:   STDOUT.puts line
17: }
18: 
19: print <<FOOTER
20: </body></html>
21: FOOTER

入力から流れてくるのは、HTML の OL 部分。ここでは、その先頭と末尾に HTML ファイルとして必要な部分を付け加えることで、全体を HTML ファイルとして仕上げている。

プログラムを小さく分割して作ることの意義は何か?

「小さく分割する」とは、プログラムを互いに独立した要素として作るという意味で、つまりはモジュール化だ。すぐにわかる利点は以下のようなものだ。

  • 理解しやすい
  • テストしやすい
  • デバッグしやすい

何よりテストしやすいことは重要だ。端的に言ってしまえば、今回のように分割することで単体テストレベルのテストが可能になるからだ。テストしやすいからこそ、デバッグもしやすくなる。テストで動作の検証が簡単に行えるなら、リファクタリングも機能拡張もぐっとやりやすくなる。

関連リンク

関連記事

twitter より (2010-09-22)

Powered by twtr2src.

2010-09-22

入力デバイスは消耗品

iMac につないだキーボード(→ Realforce 86U)の掃除をしていて思った。キーボードは消耗品なのだ、と。同じことはマウスにも言える。直接、人の手に触れ、叩かれ、動かされる、これら入力用のデバイスは、いずれ汚れ、摩耗し、劣化し、時には壊れる。TrackPad も消耗していくだろうか?

消耗品であれば、補充について考えておかなければならない。直接手に触れる物だけに、必ずしも新しい製品が馴染むとは限らない。いま使っている物に愛着があるなら生産終了となる前に同じ製品を余分に買っておくべきだ。

もう手に入らない名品

Magic TrackPad を使い始めるまではずっと Apple Pro Mouse を使っていた。2003 年 3 月にわたしにとっての最初の Mac (チタニウム PowerBook G4)を手に入れて以来の付き合いだから、7 年以上使い続けたことになる。

Mac ユーザ以外には決して受け入れられることのなかった 1 ボタンマウスだ。いや、Mac ユーザの多くも(余程、古くからのユーザでない限り)マウスに関しては多ボタンが良いと言うに違いない。Mac OS X 以降は、PC 用の USB マウスをつなげば右クリックもフツーに使えるから。ただ、この Apple Pro Mouse はクセになる。一度、こいつをクリックしてしまうと、他のマウスをクリックするのがイヤになる。

手に吸い付くように思える材質、クリック時にマウス全体が沈み込む感覚、独特の深いクリック音。どれをとっても、他のマウスでは決して味わえない体験ばかりだ。いくら言葉を連ねても、使ったことにない人には伝わらないだろうけどね。

Apple の販売するマウスが他のタイプに変わってしまってからは、これが壊れたらどうしようと不安になった。もう他のマウスに戻るつもりはなかったからだ。白でも良かったから買っておくべきだったと悔やんだ。

結局、このお気に入りのマウスは壊れることなくその役割を終えた。光学式のマウスは可動部分がないから故障の確率は低いのだろう。また、このマウスの材質は汚れをキレイに拭き取れるので劣化が目立たない。これも長く使えた要因だろう。

いくら消耗品といっても、マウスやキーボードは頻繁に交換が必要になる物ではない。けれど、だからこそ補充品を買っておくべきなのだ。安心していると同じ物が手に入らなくなるよ。

Realforce 86U の使用感

このキーボードを使い始めて 10 ヶ月。最初の頃に感じた不満はいまも解消されていない。打鍵音はやはり安っぽく聞こえるし、不要なキーもずっと不要なキーのままだ。

ただ、現行の Apple 製キーボードに戻ろうとも思わない。その理由はストロークの深さにある。キーを叩いたときにしっかりと沈み込む感覚はやはり良い。現行の Apple 製キーボードのようにストロークの浅いものは、キーを「叩く」というより「押す」感じが強い。キーというよりもボタンに思える。

他に、気になるキーボードもあるんだけどね(↓)。これのベースになっているという「Apple Extended Keyboard」は使ったことがないんだよ。叩いていて気持ちの良いキーボードだったのかな (・д・)?

テンキーが余分。それがなければ飛び付いていたかも。

ノート型の場合はどうする?

ノート型のパソコンではメーカーに修理を依頼しない限り、キーボードを交換することは難しい。メーカーによっては、(個人ユーザでも)部品としてキーボードを発注できることもあるけれど、交換にかかる手間はケーブルの抜き差しだけですむデスクトップ型とは比べ物にならない。

(昔とくらべれば)本体の価格が下がった現在、ユーザは数年使った後でキーボードが壊れたとして修理に出すだろうか? 修理の問い合わせをして「修理費は○万円になります」と言われたら、修理をためらうに違いない。新しい製品を買う方が良いと思うんじゃないか?

関連リンク

関連記事

twitter より (2010-09-21)

  • 08:32  英語を忘れそうなのでメモ。proof of concept 。プロトタイプの前段階として位置付けられているようだ。→ http://ja.wikipedia.org/wiki/概念実証
  • 08:34  ふむふむ。proof of concept は proof of technology や pilot project とは別のプロセスなのね。この辺りの言葉はきちんと定義して使わないと誤解のもとだね。→ http://ja.wikipedia.org/wiki/概念実証
  • 22:22  ドルだての買い物をするときには円高がうれしいね。RTM の pro アカウントを更新したんだが、$25 ≒ ¥2,200.- だった。
Powered by twtr2src.

2010-09-21

Blogger で作ったブログを iOS デバイス対応にする

このブログを iOS デバイスに、とくに iPad に対応させようと試行錯誤を始めたのが 8 月の下旬。そろそろ一ヶ月になるが、ようやく iPad を中心に一群の iOS デバイスに対応させる作業が完了した。新しい iPod touch (→ 第4世代になるらしい)でも確認したから Retina ディスプレイを備えた世代のモノでも大丈夫だ。

デバイスを切り替える方法の調査から、スタイルシートを Blogger の外部に置く方法の調査やら考察に進み、Google App Engine (以下、GAE) を使う方法に気付く。さらには GAE アプリでスタイルシートを切り替える方法を採り、今に至っている。

細かなことになるが、Google Font API を試したことや、iPhone シミュレータを使って iOS デバイスでの見た目を確認することを思い付いたり、Redmine でプライベートな問題追跡を始めたのも、この作業の中でのこと。

ま、一番の苦労は、Blogger の(複雑な)テンプレートとの格闘だったけどな。ちなみに、そこでは Safari の「開発」メニューから起動できる「Web インスペクタ」が大活躍だった。これなくして、Blogger のテンプレートから生成される HTML の解析は困難だったろう。ましてやスタイルの定義は不可能だったに違いない。CSS によるスタイルの定義が、どう適用されているかの表示は、Blogger のスタイルを上書きするために、なくてはならないものだった。Safari、バンザイ ヽ(`д´)ノ。

もっとも、Firefox にしろ、Chrome にしろ、最近のブラウザでは似たような機能は利用できるみたいだけどね。

スタイル定義における基本方針

多少、ブレたりもしたが、最終的には以下の方針に落ち着いた。

  1. iOS デバイスに対応する
  2. iPad (横向き)を基本サイズにする
  3. iPad (縦向き) はサイドバーを表示しない
  4. iPhone は向きに関係なくサイドバーを表示しない
  5. Mac 以外のデバイスでは通信量を考慮に入れる

作業を始めた当初は、iPhone の横向き 480px を、iPad の縦向き時のメイン領域にして、サイドバーも表示させようと考えていた。しかし、これだと iPad の向きを変えたときに、一行に表示されている文字数が変化してしまい、見づらいことがわかった。これを避けるため、縦向き時と横向き時のメイン領域の横幅を同じにしたい。かといって、サイドバーがあまり細くなっては、これまた中身が見づらい。悩んだ末、縦向き時にはサイドバーの表示をあきらめた。結果として、サイドバーが見たければ向きを変えるというユーザ体験を導入したことになった。

デバイスの向きを変えるとメニューの類が表示されるというユーザ体験は、iPad 標準アプリの「メール」や「メモ」などのものに少し似ている。

メイン領域

iPad で縦向き時にはサイドバーを表示させないので、画面幅 768px はすべてメイン領域の表示に使う。そして、横幅 768px をこのブログでの基本幅とし、Mac 向けのスタイルでもそのまま適用する。

一方、iPhone に最適化する際は横向き 480px での表示を基本とし、縦向きでの見た目は最低限の調整に留めた。なので、iPhone でこのブログを見るときには、なるべく横向きにして見てほしい。

サイドバー

メイン領域が 768px だから、サイドバーは iPad の横向き時の幅からメイン領域を除いた部分に表示する。つまり、1024 - 768 = 256 で 256px になる。

メイン領域と同じく、サイドバーの幅 256px も Mac 向けスタイルにもそのまま適用する。

Mac 向けのレイアウト

iPad の横向き時と同じレイアウトになる。縮尺は正確ではないけれど、27 インチ iMac で表示させた場合、横幅 768px + 256px は、だいたい左図のようになる。

ちなみに、Mac 上の Safari で 1024px のウェブページを表示させるにはウィンドウのサイズを 1040px にすると良い(→ AppleScript を使って URL を Safari のタブで開く)。余分な 16px はスクロールバーの分だ。

通信量に対する考慮

大した量じゃないという気もするのだが、Google Font API を使うのは Mac のみとした。

そもそも、iPhone の場合で、common と iphone の 2つ CSS ファイルで約 18 KB ある。通信量のことを気にするなら、これ自体も十分ムダだよな。

まとめ

Retina ディスプレイ搭載の iPhone 4、iPod touch (4G) への対応に少し不満が残っているものの、ほぼ満足行くスタイルに仕上がった。iPad や Mac で見たときはもちろん、iPhone で見ても、コンテンツを見る分には十分だ。

サイドバーをふくめたナビゲーション全般で改良の余地はあるけれど、HTML と CSS でやれる範囲はここまでだろう。この先は、JavaScript の活躍する領域になる。ただ、Blogger のテンプレートとの格闘には良い加減疲れたので、これ以上手を加えることはないと思う。スタイルのバグ修正や、ちょっとした新スタイルの追加はするけれど。

むしろ、Blogger は記事の投稿だけに使って、記事を見るには別のアプリやウェブアプリを使う方が良いんじゃないか、と思い始めている。先のナビゲーションの改良なんかもやりやすい。特定のブログを表示するためだけの iPhone アプリがあっても良いじゃないか。ブログシステム全体を作るのは大変でも、見るだけに特化するなら HTML + CSS でやることの延長として考えられる。

関連リンク

関連記事

twitter より (2010-09-20)

  • 00:39  ふうむ。ウチの iTunes のミュージックライブラリは 21.9 GB になっている。落語を除いてこれだからな。
  • 01:39  なんだ。HDR はやっぱり iPhone 4 専用か。iPod touch (retina) じゃダメなのか (´・ω・`)
  • 02:14  iPod touch、ケースはいらないけど、アンチグレアフィルムはやはり必須だな。旧モデル用のはサイズが合わないだろうし。パワーサポートが出してくれるのを待つしかないか。
Powered by twtr2src.

2010-09-20

フリー ([著]クリス・アンダーソン, [版]NHK出版)

フリー―〈無料〉からお金を生みだす新戦略
クリス・アンダーソン
日本放送出版協会 ( 2009-11-21 )
ISBN: 9784140814048
おすすめ度:アマゾンおすすめ度

ぼちぼち読み進めてきて、もうじき読み終わる。改めてフリー(自由と無料)について考えさせられる内容だが、衝撃としては同じ著者による「ロングテール」ほどではない。正確に言えば衝撃を受けたのは「ロングテール」という本を読んだときではなく、ネットのどこか(誰かのブログだったのかな)でロングテールという言葉とその意味するところを知ったときだったけど。

フリーとフリー

英語の free という言葉は 2 つの意味を持つ。日本語ではそれぞれ「自由」と「無料」と訳される。著者はこの言葉をその語源から探り、2 つの意味をそれぞれ「奴隷からの自由」と「費用からの自由」と説明している。通常、ヒトが自由と口にするときは前者を意味する。もちろん、無料と言ったときは後者の意味になる。著者は、本書で扱うのは後者「費用からの自由」なのだ、と明言している(p.28)。

確かに、free と言う言葉が混乱を生む可能性があることは、21 世紀に生きるプログラマなら誰でも知っている。いや、20世紀の終盤を生きたプログラマなら、かな。ともあれ、free を語るとき、自らがどちらの free を念頭に置いているかを表明しなければ、誤解を生むことになりかねない。人は誰しも自分の立場で理解し、考えるからね。

一方で、本当に「奴隷からの自由」と「費用からの自由」を分けて考えられるのだろうか、っていう疑問もあるんだけどね。

フリーの形

なぜフリー(無料)が成立するのか? 著者はそれを 4 つの形に分類している。

  1. 直接的内部相互補助
  2. 三者間市場
  3. フリーミアム
  4. 非貨幣市場

いかつい単語に幻惑されるが、それぞれの説明を聞けばどれも馴染みのあるものばかりだ。

1. は何かをとんでもなく安く(無料)にして、他の高い物を買わせる、という方式。スーツの量販店で 2 着目は無料です、と言われるアレだ。単純に言って、安い(無料)の方の価格が、そうでない方の価格に上乗せされているだけのものだ。

2. は TV の放送(と CM )でお馴染みだ。報道番組であれ、バラエティであれ、ドラマでも映画でも、無料で見られる代わりに CM を見せつけられるというやつだ。

3. は最近のネットサービスで多い。RTM も Evernote もみんなこの方式を取っている。金を払ってくれるヘビーユーザ(またの名を信者)のおかげで、それほどでもない一般ユーザは無料で使うことができる。

4. は...。フリーの形としては実はこれが一番重要なんだが、一番わかりにくいものでもある。言葉にすると、金銭とは別の形で支払っている、となる。けれど、この言葉ではとうていすべてを言い尽くせてはいない。本書でも第12章を丸ごと、この「別の形」で支払うことについて解説してくれているが、納得できないんだよねえ。

アトムとビット

上述の 4 つの形のうち、1. と 2. はどちらも以前から存在するものだ。2. について Google が事情を大きく変えたりもしたが(p.294)、新しい何かというわけではない。一方、3. についてはネットのサービスでもなければなかなか実現が難しいものだ。これが可能なのは、ネットサービスが著者の言う「ビット」の世界に構築されているものだからだ。

「ビット」とは複製が完全かつ容易なモノのこと。つまり、デジタルなコンテンツ(とくにソフトウェア)のことだ。

一方で、デジタルではない従来の物のことを著者は「アトム」と呼ぶ。アトム、すなわち原子であり、物理的な実体をともなった物であったり、人(の提供するサービス)がやったり取ったりされる世界のことだ。こちら側では、複製は高価であったり、そもそも不可能だったりもする。

両者の違いは何か? つまるところ「複製可能性」と「複製容易性」になる。後者も重要だが、とくに前者が肝心なところ。というのは、ビットには可能なこれがアトムでは不可能だから。どれほど費用をかけたとしても、物(や人)の完全な複製はできない。根源的な稀少性がそこにはある。ビットではこれが可能で、かつこの数十年で複製の費用がどんどん低下してきた(だいたいは Intel のおかげ)。

単純に言ってしまえば、安価に作れる(製造できる)ものは、安価に提供できる、っていうことだ。もし、タダで作れるなら、タダで配ったって問題ない。もちろん、実際問題としてタダで作れるってことはないから、いくらかのコストが発生する。ただし、それはとても低い。とても低いから、従来は(アトムの世界では)、不可能だったり困難だったりした方法でもコストの回収ができるようになる。これが現在、あちこちで見かける「フリー」を支えているものの正体だ。

で、その(とても低い)コスト回収の仕組みを分類すると、上述の 4 つになる、というのが著者の主張だ。

本当にフリーなのは?

ただ、4. の形のフリーをどう考えるか、それが問題として残る。費用を注目と評判で回収すると言うだけでは、提供者の動機を説明し切れていないと思う。

これについては、またおいおい。

ま、結局のところ、経済活動のような複雑な行為のシステムはよくわからないってことかもしれないけどな。

おまけ

忙しい人のための著者の主張を手っ取り早く知るための読み方を提案してみる。

  1. 『第2章 「フリー」入門』を読む
  2. 『第16章 「お金を払わなければ価値のあるものは手に入らない」』を読む

この 2 つの章を読めば、まあ大体はわかる。とくに、第16章を読めば、著者の主張(と論理)の弱いところも見えたりしておもしろい。時間があれば目次を眺めて気が引かれたところを拾い読みするのも良い。

あと、キーワードとしての「稀少」と「潤沢」かな。「フリー」を加えた、この 3 つの単語を覚えれば、本書を読んだって言い張れる。

関連書籍

関連リンク

  • The Long Tail (著者クリス・アンダーソンのブログ)

関連記事

twitter より (2010-09-19)

  • 03:25  すぐに英語での表現を忘れるのでメモ。「稀少の経済」の方が "The Economy of Scarcity" で、「潤沢の経済」の方が "The Economy of Abundance" 。→ http://bit.ly/adU0IW
  • 05:24  Redmine の使い方、あれこれ。→ http://forza.cocolog-nifty.com/blog/redmine/index.html
  • 05:28  Redmineのissue(チケット) にはbug, feature, suportの3種類が標準で定義されている。bugとfeatureは自明だけど、「supportって何?」と思ってググって見つけた。なるほど「その他のタスク」か。→ http://bit.ly/dub0yP
  • 05:30  ソフトウェア開発のプロジェクトには、機能(の設計と実装)、バグの修正以外にも、やらなくてはならないことがあれこれある。そういうのを support として issue 発行するわけか。開発環境やテスト環境の構築もそうだし、リリース作業なんかもここに入れれば良いね。
Powered by twtr2src.

2010-09-19

Retina ディスプレイ搭載の iPod touch

発表されてすぐに発注したのが 9/2 の早朝、出荷は 2 〜 3 週先だと言われしょんぼり。待つこと 2 週間。出荷を知らせるメールが届いたのが 9/16。上海からやって来るとわかる。待つこと、さらに 3 日。ようやく、今日、新しい iPod touch をゲット。

まずは、写真をいくつか載せておく。

iPod touch、到着
薄い! (; ゜Д゜)
背面は iPod 伝統の鏡面仕上げ
電源、オン!
アクティベーション完了

ファーストインプレッション

やはり一番のオドロキは、その薄さだ。iPhone 3GS と並べて見ても一目瞭然。こんなに薄くて大丈夫なのか、と心配してしまうほど。初代の iPod のことを思い出すと嘘のような薄さだ。

最近は、iPhone しか使っていないし、その前でもすでにウチの iPod の主力は nano に移っていた。だから久しぶりに鏡面仕上げの背面を見て、これが iPod だよな、と思い出した。指紋がベタベタつくし、ケースに入れずに使うと傷だらけになったりするけど、それも iPod らしさってもんだ。

まだ、アプリを同期させていないので、ebook を表示させたときの Retina ディスプレイの見え方なんかは試していないけど、それはおいおい。

ウェブページを Retina ディスプレイ搭載機器対応にする

デバイスの検出

すでに、このブログは iPhone (3G/3GS) 対応になっている(iPad 対応はそれなり)。その仕掛けの主要部分が GAE アプリによるスタイルシートの切り替えだ。その肝になっているのが、リクエスト機器が送ってくるユーザエージェント文字列によるデバイスの検出だ。こう書くと大袈裟だが、やっていることはユーザエージェント文字列の中から "iPhone" や "iPad" といった文字列を探しているだけだ。

iPod touch 対応にするため、まずは上述の仕掛けの中で iPod touch を検出する。そのためには iPod touch が送るユーザエージェントの文字列を知らなければならない。調べれば(ググれば)わかるだろうが、ここは実機で確かめることにした。そのためのテストプログラムがこれ。

(useragent.cgi)
 1: #! /opt/local/bin/ruby1.9 -w
 2: # -*- coding: utf-8 -*-
 3: 
 4: # useragent.cgi: check the user-agent string sent by client devices.
 5: # Last modified: 2010-09-19
 6: 
 7: require 'cgi'
 8: 
 9: cgi = CGI.new
10: 
11: puts "Content-type: text/plain"
12: puts
13: 
14: print cgi.user_agent
15: STDERR.puts cgi.user_agent

CGI を実行できるフォルダ(Snow Leoaprd の Apache なら標準で /Library/WebServer/CGI-Executables)に置いてブラウザからアクセスすれば良い。iPhone や iPod のようなデバイスだと、ブラウザの画面から Mac にコピペできないので、標準エラー出力にもユーザエージェント文字列を書き込んでいる。こうすると Apache のエラーログ(Snow Leopard の標準設定なら /var/log/apache2/error_log)に書き出される。

ついでなので、iPod touch 以外のデバイスでもユーザエージェントを調べてみた。

各種デバイスのユーザエージェント文字列
DeviceOSUser-Agent
Mac Snow Leopard 10.6.4 Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_4; ja-jp) AppleWebKit/533.18.1 (KHTML, like Gecko) Version/5.0.2 Safari/533.18.5
iPhone 3GS iOS 4.1 (8B117) Mozilla/5.0 (iPhone; U; CPU iPhone OS 4_1 like Mac OS X; ja-jp) AppleWebKit/532.9 (KHTML, like Gecko) Version/4.0.5 Mobile/8B117 Safari/6531.22.7
iPad iOS 3.2.2 (7B500) Mozilla/5.0 (iPad; U; CPU OS 3_2_2 like Mac OS X; ja-jp) AppleWebKit/531.21.10 (KHTML, like Gecko) Version/4.0.4 Mobile/7B500 Safari/531.21.10
iPod touch
(Retina Display)
iOS 4.1 (8B117) Mozilla/5.0 (iPod; U; CPU iPhone OS 4_1 like Mac OS X; ja-jp) AppleWebKit/532.9 (KHTML, like Gecko) Version/4.0.5 Mobile/8B117 Safari/6531.22.7

iPhone と iPad を "iPhone"、"iPad" で検出したように、iPod touch も "iPod" で検出すれば良いことがわかる。

Retina ディスプレイの検出

iPhone にも 3G/3GS と 4 があるように、iPod touch にも Retina ディスプレイを搭載している新モデルと旧モデルがある。これら新旧のモデルの区別には、今のところ CSS のメディアクエリーを使うしかないようだ。

iPhone 4 の CSS を切り替える方法」にあるように -webkit-device-pixel-ratio: n で新旧モデルを切り替える。n = 1 なら旧モデル、n = 2 なら Retina ディスプレイ搭載のモデル、となる。

あれ、どちらも 320 * 480 なんだけど……

実際にスタイルを定義し始めるまでは、上述のようにして Retina ディスプレイ搭載モデルを検出すれば、640 * 960 の画素数の画面として扱えると思っていた。しかし、新モデルも、Safari から見ると、デバイスの画素数はやはり 320 * 480 なのだ。1/2 ピクセル単位で指定できるのでない限り(できる? できないよね?)、スタイルを定義する観点から言えば、新旧モデルに差はないということだ。

iPhone 4 の CSS を切り替える方法」の最後に少し書かれているように、JavaScript を使って、viewport を書き換えるという方法もある。ただし、このページに載っているコードそのままだと、iPod の向きを変えたときに width が変わらない。もう少し、工夫が必要らしい。

小さい文字がくっきり見える

CSS の定義上は変わらないと言っても、実際に見比べれば Retina の解像度には目を見張るものがある。はっきり言って全然別ものだ。わかりやすい例を挙げると、Retina ではより小さなポイント数の文字がはっきりと読める。iPhone 3GS では 10 pt より小さいと見えにくくなるが、Retina の iPod touch なら 9pt でも余裕で読める。

この違いならスタイルに反映させることができる。つまり、Retina モデルでは少し小さい文字を表示に使うというものだ。実際の表示を写真で示す。以下の写真を iPhone/iPod touch 等で見るときは、本体を横向きにしてほしい(サムネイルを 400px にしてあるので)

文字表示の比較
iPhone 3GS { font-size: 11pt; }
iPod touch (Retina) { font-size: 10pt; }
iPhone 3GS { font-size: 11pt; }
iPod touch (Retina) { font-size: 10pt; }

iPod touch のフォントを 9pt にしていないのは、さすがに目が疲れそうだから。くっきりと読めるということと、楽に読めるということは別物だからね。

関連リンク

関連記事