アペフチ

『Flutter×Firebaseで始めるモバイルアプリ開発』をシンタックスハイライトして読む

Flutter×Firebaseで始めるモバイルアプリ開発』という本を読み始めて、ソースコードが白黒なのに気が付いた。 シンタックスハイライトされていないソースコード

ので、例によって、シンタックスハイライトしてみました。 シンタックスハイライトされたソースコード

Pirkaというコマンドラインツールを使います。インストールは gem コマンドで。

% gem install pirka

次に、シンタックスハイライト情報が入っている「ライブラリー」ファイルを更新します。既にPirkaを入れてくれている人も、今回新しくライブラリーを追加したので、この操作は必要です。

% pirka update

最後にダウンロードしてきたファイルを引数に、 pirka コマンドを実行します。

% pirka ~/Downloads/Flutter×Firebaseで始めるモバイルアプリ開発-1.0.0.epub

すると、本の中のコードがハイライトされています。(上書きしちゃうので、必要であればバックアップしておいてください。)

僕は達人出版会で買って、そこからダウンロードしたEPUBファイルを元にハイライト用のデータを作ったので、他の所で購入した人はもしかしたらうまくいかないかも知れない。

Flutter×Firebaseで始めるモバイルアプリ開発
下畑 翔, わみ
インプレスR&D
発行日: 2018-12-21
対応フォーマット: PDF, EPUB

相談には乗りますので、次のどちらかにご連絡ください(他のでもいいけど、この二つだと嬉しい)。

みんな元気。 - 小説の思い出

ふと「矢を止める五羽の梔鳥」(舞城王太郎)を読みたくなって、検索したら『みんな元気。』に入っているとのことだったので、Kindleで買った。

(表紙に全然見覚えがない。僕は本当にこれを読んだことがあるんだろうか。)

と、書いてはみたけれど、実のところこの小説を読みたかったのかは分からなかった。

矢を止める五羽の梔鳥

舞城王太郎の小説は読みやすいし分かりやすいのだけど、「何か全然意味分からないのあったなあ」という気持ちの記憶があって、それと紐付いている言葉が「くちなし鳥」だったので、検索して出てくる「矢を止める五羽の梔鳥」だな、と思って、これを読みたかったのだと思い込んでいた。思い込んでいたというのは、実際読んでみたら、これじゃなかった。内容はもう殆ど忘れていたし、面白かったので、読んでよかったなとは思ったけど、これではなかった。

夜山火事を見ていたらあれよあれよと街(多分、小さな集落。いつもの舞城王太郎なら)で起こってる連続殺人と関連を持ってしまった語り手が、舞城王太郎得意の[1]「言葉遊び的な頓智」で解決したんだかしなかったんだか……という話。

「昔読んだあの小説が読みたい」の「あの小説」を除いた「小説を読みたい」の気持ちもまだ全然抱えたままだったので、そのまま他のを読んでみようと思って、目次を見てみる。

みんな元気。

面白かった記憶はある。家族の話だった筈。

Dead for Good

全然思い出せない。

我が家のトトロ

全然思い出せない。

矢を止める五羽の梔鳥

今読んだ奴。

スクールアタック・シンドローム

分かりやすくてすかっとする奴だった気がする。

(もし読みたかった小説がこの本に入ってるとして)消去法で、候補は「Dead for Good」と「我が家のトトロ」だ。

我が家のトトロ

名前がキャッチーだったのでこちらから読んだ。外れたら「Dead for Good」を読むとも決めてなかったしそもそも必ずしも例の「読みたかった小説」を求めてたわけじゃなくて何となく気になったから読んだので「こちらから」というのはおかしいけど。

冒頭が駅のトイレでうんこ漏らすというエピソードで、そこまで読んでもう思い出して、当時は知らなかったけど後から31歳にしてうんこをもらしましたという話を知って「ああこれかあ」となって、でも別に再読しなかった作品。主人公は(もらしました記事と同じ)コピーライター出身で、元同僚、現小説家の濱田淳という人とこんなやり取りをしていたのは読まずとも思い出せた。

「面白い小説書いてたって、それが他の人に気づいてもらえなかったら駄目だろう」と僕は言う。
「面白い小説が書ければ、皆気づきますよ」と濱田は言う。「当然じゃないですか。皆面白い本が読みたくて探してるんだから」
「甘いよ。いいものがあれば皆がそこに自然に集まるようなら、いいものだけが売れるようなら、広告もコピーもそもそもいらないだろ。俺らだって生きていけてなかっただろ。でも広告はある。何でかって言うと、それが必要だからだろ。いいもんだって、放っておいたら誰も気付かないんだよ。だから広告が要るんだ」
「逆ですよ。広告があるせいでつまんないものに人の目が行くんですよ。本当なら人の目はいいものだけを探しているのに、偽物に騙されて良くないのもにも目が行っちゃって、いいものに気付きにくくなってるんですよ」
「何言ってやがんだ馬鹿。お前さ、自分が広告打ってきた商品、つまんないもんだと思ってた訳?だとすればお前の広告が間違ってたんだよ。俺は俺の広告が正しいと思って打ってたんだから。いいものだから皆に伝えたいって気持ちで広告打ってたんだから」
「まーた上口さんはそんな、綺麗事ばっかり。そんなの絶対嘘ですよ」
「嘘じゃないよ」

……と議論は続く。少年まんがで育ったためか僕は視点人物に感情移入して読むんだけど、初めて読んだ当時「面白いだけじゃだめだ」という、自分とは反発する価値観を視点人物が語りだして、感情的に混乱したのを思い出した。

今読むと、視点人物の方が自分の言っているのことをきちんと自分の中から出した物なのか反射的に正当化しちゃうために出した物なのか分からなくて、それはこの後の議論を読んでも分からないし地の文でも後から「実は……」とか「本当なのに……」とかは書かないから分からなくて、そこが面白い。読者に考えさせるから面白い、のではなくてそんなこと大事なことじゃないんだよという態度がいいなあと思う。

Dead for Good

これが目当ての小説だった。同じ本に入っていてラッキーだった。……とは思うんだけど、もしかしたら偶然ではないのかもなとも思う。前から順番に「みんな元気。」「Dead for Good」と読んで「んんん?」となったけどそのまま「我が家のトトロ」「矢を止める五羽の梔鳥」と来て、タイトルの説明(龍安寺のつくばいに刻まれた『吾唯知足』から学ぶ人生の深い意味とは・・・)が面白いなと思ったその視覚的な興奮と「んんん?」という感情的なもやもやを残していたのが結び付いて記憶に残っていたのかも知れない。人間っぽい。

特に盛り上がりもないし、体験間の繋がりも特にない日常を描いていって、それは気持ちのいい物では全然ないし、だからといって考えさせるというのも違って、意味とか作者のメッセージという物を求めていた当時の僕は全然分からなかった。最初にも書いたけど舞城王太郎は分かりやすいし面白いという印象を持っていたから、それに反した感じの小説で驚いた。のだけど、今読むと昔ほど分からないって感じじゃない。小説が分かるというのは小説を読むことでしか分からないので、言葉では表さないけど、前読んだ時より分かる。関係ないエピソードは響き合ってる。

こういう感じは今ならはっきりと僕は好きだなと言えるし、「Dead for Good」の時は分からなかったけど「美味しいシャワーヘッド」[2]の面白さに通じていたのだなと今なら思う。

ややオフトピックだけど、作中の重要人物である兼益かねますのようなひとについて考えることがある。兼益は本物のサディストで、主人公を騙して薬で意識を奪って電流を流して後遺症を残させたり、今ではどこか紛争地域でアンチテロリストとしてテロリストを拷問したりしている、どうしてもそうしないといられない人間だ。そういう人はいる、と思う(会ったことはないか少なくとも気付いたことはないけど)。そういう人は例えば日本のような平和な国[3]に生まれてしまってはその欲求を満足させることは一生できないんだろうか。それは幸せなんだろうか。その人が欲求を満足させることは絶対に許されないことなんだろうか。つまり、社会に仇為す生理的欲求は、満たされてはいけないんだろうか(この場合、それが許される国に行くというのは話を逸らせているだけ)。ということを、たまに考える、というより、ぼんやり思う。これを考えるのは僕自身が今までほんと幸せな環境で暮らしてきたということを示唆するのは分かってはいるけど、考えてしまう。落ちはない。

特に好きだなと思ったシーンは主人公が風呂場にいる所。別に彼氏のいる女の子が、主人公のことが好きだということをその彼氏にして、彼氏が主人公の部屋の前に来てドアをどんどん叩いている時、主人公は風呂場のバスタブに座って包丁を持ったまま、その彼氏が入って来るのを期待している。バスタブというのは兼益に押し込められて電流を流されて後遺症を齎した場所で、ドアを叩いているのが女の子の彼氏でも、包丁を握って座り込んで入って来るのを期待している相手は兼益だ、と自分で分かっているというシーン。いいな。

みんな元気。

「Dead for Good」を読んで、小説を読みたい気分がまだ続いていたので折角だからと表題作も読んだ。

ある日別の家族がやってきて、自分の妹とその家族の男の子を強制的に交換して去っていった……という所からスタートする、家族という感覚についての話。かな。

なんか昔ほどのめり込めなくて、途中飛ばしながら読んでしまった。交換されていなくなってしまった妹への思いと、交換されて弟になってしまった少年への思いは、日本語になっている家族愛とは違うけどやっぱりそこにそれなりの形の愛情はあるのだな、だからみんな元気。という話で、そう感じ取った感覚はよく憶えていた[4]。のだからちょっと退屈に感じてしまったのだろう。主張の分かっている話を読んでもな……というのと、その感覚自体は今の僕には当たり前だから。

でも結構、本題とは違う、昔は気にもしなかったような箇所が今読むと面白くて、そういう面白さがあるのは本物の小説だなあ。

主人公(枇杷)には妊娠期間七か月で生まれて同じ学年の姉(ゆり)がいて、顔が似ていて、その姉の彼氏が自分に色目を使ってきたり逆があったりが毎回で、嫌になって付き合ってもセックスをしなくなった、という経歴があるのだけど、彼氏との会話でこういうのがある。

私が思うに、宮本いさおのいいところは大きな手と何かたくらんでそうに見えて全然たくらみなんてない変な顔と腹のすっきりしたところで、私が雨の中を傘をささずに歩いていると「そういうのが楽しければ好きにすれば?」とか言いながら自分の傘も閉じて仏頂面を作って黙って歩いて百メートル先で突然「いさおもうパンティまで濡れ濡れ」とかいきなり言って爆笑を誘うところだったし、野口健司のいいところは鼻筋がまっすぐ通っていて先が丸くて、キスするときにその鼻先がとんとん、とんとん、と私の頬、胸、腹、太ももをノックしてくれることや、寝言で「こら、そこのカレーからいい加減その猫つまみ出せ」と怒ってたりすることや、うっかり自分がおならをしちゃったときに、その部屋やら車両やら道ばたから私の手を引っ張り、ときにはお姫様だっこで「ギャー逃げろ」とか言って駆け出したりすることだったし、他の彼氏にもいろいろ細かい楽しいところ、観察すべきところがあったのだが、完璧の南田洋平に言わせれば、宮本いさおも野口健司もその他の元彼たちも、恋愛の不可能性においてこそ私を惹きつけるらしい。
「ゆりちゃんの元彼とか今彼となんて絶対嫌と言いながら、その絶対嫌なところでしか枇杷は相手を選べなかった訳よ」と南田は言う。「と言うか、選ばなかった訳だ。うまくいかないからこそいいんだよな。ゆりちゃんの元彼と今彼以外のところで付き合いが始まると、わざわざゆりちゃんの元彼今彼のことを話題に持ち出してきて問題にして、問題ないところにいちいち問題作って結局駄目にしちゃったりする。そういうの見てれば分かるけど、《ゆりちゃんの元彼とか今彼》なんて、枇杷にとっては男と別れるための道具に過ぎないんだよな。で、その道具で磨いて余計な部分を落として発展させて出てきたセックスレスも、結局同じ道具なんだよ」
 日曜日の朝に、調布のパルコのレストランで、桜が散って落ち着いて、日差しの中にいるのにそんな会話が始まって、私は言う。「これって別れ話じゃないよね」
「違うよ」と南田が言う。「ただの会話。でもただの会話だけど、単なる会話じゃなくて、俺ちょっとはっきりさせておきたいんだよね」
「私の問題とか?」
「全然違う。俺は、枇杷の持ち出すどんな道具にも負けないし、道具なんかいくら使ったって枇杷のことを諦めたりしないってこと」
「ありがとう」
「感謝されるようなことでもないよ。そういうことじゃない。俺を諦めさせようとするなら、もっと本当の理由を持ってこなきゃいけないってこと」
「本当の理由って?」
「唯士君とどうするか、ちゃんとはっきりさせなきゃいけないってこと」
 南田はあっという間に全部言ってしまう。何も待たない。譲歩はないが、ごまかしもない。先延ばしもないが、せき立てる感じもない。南田がそれを持ち出すと、このときこそそれを持ち出す最良のタイミングだったんじゃないかと思う。そしてそれはたぶん、南田の人格とは関係なく、実際に最良のタイミングだったのだ。遅すぎたくらいだとすら私は思う。

昔通過した筈なのに、何だか今も囚われているぞ? という感情を思い出させる話。回りくどく自分を騙すことで、自分の苦しいことから自分で目を逸らしているのにそうと自分で気が付かない、という状態、克服したと思ってもやっぱりすぐ陥っちゃいますね。こういうのは自分では「武器」だと思っているわけだから、疑いが向かないわけです。

舞城の作品はいつも愛情は尊くて正しい物で、だからか主人公の恋人や配偶者は男も女もみんなかわいくかっこよく愛おしく描かれるよね。


1. 度々やるので舞城王太郎独特のという気にはなるけど、清涼院流水のJDCシリーズに出てくる探偵に由来するんじゃなかろうか。
2. なんか単行本で読んだような気でいたけど今検索したら単行本にはなってなくて、短編集『キミトピア』に入っているらしい。この本を読んでいないのは間違いない。ので多分、当時の「新潮」で読んだんだろう。
3. というほど実は平和でもないけどね……。
4. これが作品の主張だとは言わない。今読んでもちょっとずれてるなとは思う。

RubyでのCSV処理はApache Arrowが速い

昨日RubyでのApache Arrowの使い方(Parquetもあるよ)という記事を書いて、Rubyでデータを扱う時に

FastestCSV > Arrow > Parquet > CSV

の順で速くなる、ということを書いた。これにArrowやRubyのCSVの開発者・メンテナーである@kouさんからこんな指摘を貰った。

each_chunk なんてAPIがあったんだ……! やってみました。

                                        user     system      total        real
CSV(Ruby 標準添付CSVライブラリー)           850541456
 22.359375   0.390625  22.750000 ( 22.763864)
CSV(FastestCSV RubyGem)           850541456
  1.531250   0.140625   1.671875 (  1.718251)
CSV(Red Arrow each_record_batch)  850541456
 10.875000   1.812500  12.687500 ( 10.767980)
CSV(Red Arrow each_record)        850541456
 71.437500   1.171875  72.609375 ( 72.159371)
CSV(Red Arrow 対象カラムだけ each)       850541456
  3.734375   1.843750   5.578125 (  3.226278)
CSV(Red Arrow 対象カラムだけ each_chunk) 850541456
  0.859375   1.437500   2.296875 (  0.478962)
Arrow(each_record_batch)          850541456
  9.859375   0.078125   9.937500 (  9.992725)
Arrow(each_record)                850541456
 72.781250   0.171875  72.953125 ( 73.775244)
Arrow(対象カラムだけ each)               850541456
  2.125000   0.015625   2.140625 (  2.133398)
Arrow(対象カラムだけ each_chunk)         850541456
  0.015625   0.000000   0.015625 (  0.024413)
Parquet(each_record_batch)        850541456
 11.062500   2.078125  13.140625 ( 10.599412)
Parquet(each_record)              850541456
 13.343750   0.906250  14.250000 ( 12.923848)
Parquet(対象カラムだけ each)             850541456
  3.703125   0.718750   4.421875 (  3.152494)
Parquet(対象カラムだけ each_chunk)       850541456
  0.656250   0.468750   1.125000 (  0.243396)

見ての通りArrowが圧倒的に速い。

Arrow > Parquet > FastestCSV > CSV

となってますな。

CSVフォーマットを扱うのでも、FastestCSVよりArrowで each_chunk メソッドを使う方が速い。素晴らしい。

参考リンク

ベンチマークスクリプト

require "benchmark"
require "csv"
require "fastest-csv"
require "arrow"
require "parquet"

CSVFILE = "sample-data.csv"
ARROWFILE = "sample-data.arrow"
PARQUETFILE = "sample-data.parquet"

Benchmark.bmbm do |x|
  x.report "CSV(Ruby 標準添付CSVライブラリー)" do
    amount = 0
    CSV.foreach CSVFILE, headers: true do |row|
      amount += row[4].to_i
    end
    puts amount
  end

  x.report "CSV(FastestCSV RubyGem)" do
    amount = 0
    headers = true
    FastestCSV.foreach CSVFILE do |row|
      if headers
        headers = false
        next
      end
      amount += row[4].to_i
    end
    puts amount
  end

  x.report "CSV(Red Arrow each_record_batch)" do
    amount = 0
    Arrow::Table.load(CSVFILE).each_record_batch do |records|
      records.each do |record|
        amount += record[4].to_i
      end
    end
    puts amount
  end

  x.report "CSV(Red Arrow each_record)" do
    amount = 0
    Arrow::Table.load(CSVFILE).each_record do |record|
      amount += record[4].to_i
    end
    puts amount
  end

  x.report "CSV(Red Arrow 対象カラムだけ each)" do
    amount = 0
    Arrow::Table.load(CSVFILE).find_column(4).each do |record|
      amount += record.to_i
    end
    puts amount
  end

  x.report "CSV(Red Arrow 対象カラムだけ each_chunk)" do
    amount = 0
    Arrow::Table.load(CSVFILE)[4].data.each_chunk do |array|
      amount += array.cast(Arrow::Int64DataType.new).sum
    end
    puts amount
  end

  x.report "Arrow(each_record_batch)" do
    amount = 0
    Arrow::Table.load(ARROWFILE).each_record_batch do |records|
      records.each do |record|

        amount += record[4].to_i
      end
    end
    puts amount
  end

  x.report "Arrow(each_record)" do
    amount = 0
    Arrow::Table.load(ARROWFILE).each_record do |record|
      amount += record[4].to_i
    end
    puts amount
  end

  x.report "Arrow(対象カラムだけ each)" do
    amount = 0
    Arrow::Table.load(ARROWFILE).find_column(4).each do |record|
      amount += record.to_i
    end
    puts amount
  end

  x.report "Arrow(対象カラムだけ each_chunk)" do
    amount = 0
    Arrow::Table.load(ARROWFILE)[4].data.each_chunk do |array|
      amount += array.cast(Arrow::Int64DataType.new).sum
    end
    puts amount
  end

  x.report "Parquet(each_record_batch)" do
    amount = 0
    Arrow::Table.load(PARQUETFILE).each_record_batch do |records|
      records.each do |record|
        amount += record[4].to_i
      end
    end
    puts amount
  end

  x.report "Parquet(each_record)" do
    amount = 0
    Arrow::Table.load(PARQUETFILE).each_record do |record|
      amount += record[4].to_i
    end
    puts amount
  end

  x.report "Parquet(対象カラムだけ each)" do
    amount = 0
    Arrow::Table.load(PARQUETFILE).find_column(4).each do |record|
      amount += record.to_i
    end
    puts amount
  end

  x.report "Parquet(対象カラムだけ each_chunk)" do
    amount = 0
    Arrow::Table.load(PARQUETFILE)[4].data.each_chunk do |array|
      amount += array.cast(Arrow::Int64DataType.new).sum
    end
    puts amount
  end
end