愛と勇気と缶ビール

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

村田沙耶香「コンビニ人間」

ブログ復帰?を兼ねて、なんでも書くことにした。今までは色々なトピックのブログの書き分けを試みては止めてを繰り返していたのだけど、面倒なのでもう止めることにする。

僕は近年の芥川賞・直木賞作品などには目もくれないという保守的読書家気取りのイヤミったらしい人間だったのだが、「コンビニ人間」はあまりに評判が良かったので電子書籍版を買って読んだ。かなり面白かった。

コンビニ人間

コンビニ人間

何がいいって、主人公のコンビニ人間たる女性と対比して「人間、気持ち悪いな〜」というのがまざまざと感じられるところ。今の社会でいわゆる「普通の人」がする普通のふるまいがいかに気持ち悪いかがビシビシ伝わってきて、それが不快なのではなく面白い。違った目で切り取られた世界を眺めること、やっぱりそれが小説の醍醐味の一つなのだなあ、と。

「コンビニ人間」の面白いところは普通の人間の気持ち悪さなのだが、その気持ち悪さを感じるためにはやはり主人公の側に立つ必要がある。かといって主人公は共感を呼びやすい人物でも何でもないのだけど、いつの間にか読者はコンビニ人間の側に立っている。コンビニ人間の側に立つ視点を持たなければこの小説の面白さはやっぱり味わいにくいと思う。

けど普段の生活では僕らは普通の人の側に立ってあれやれこれやらしているわけで、主人公と同様の感性を持って暮らしているわけではない。逆に、コンビニ人間の視点に立つことなく普通の人の目線のまま「これコンビニ人間の方が完全におかしな人間で、他の人は何もおかしくないよね」という姿勢を徹頭徹尾貫いて読むような人がいたとして、その人にとってこの小説が面白いかどうかはよく分からない。

けど芥川賞を取ってけっこう売れているということは、多くの人はやっぱりコンビニ人間の目から普通の人の生活を眺めて「気持ち悪いな〜」という面白おかしさを味わったのだと思う。その多くの人はやっぱり普段は普通の人として振る舞っているわけなので、そういう僕らが普通でない人の視点に同化して小説を読んで、楽しめるというのは一体どういうことなのだろうか?

この話に結論はないのだが、すごく多くの人が「普通の人が気持ち悪く見える視点に立つ小説」を読んで楽しめるというのは、不思議なことだ。あるいは僕の楽しみ方が他の人と違うのかもしれないが、とにかく僕は不思議に思っている。

話は変わるが、(そして実は変わっていないのだが、)歌人である枡野浩一さんが漫画家の古泉智浩さんとやっているネットラジオ「本と雑談ラジオ」でもコンビニ人間が話題にあがっている。

booktalkradio.seesaa.net

このラジオを聴いて「コンビニ人間を読みたい!」と思う人はあまりいないと思うが、若干気持ち悪いおじさん二人がうにゃうにゃ喋っているラジオに興味がある人は聴いてみるといいかもしれない。

このラジオの別の会で紹介されている「しろいろの街の、その骨の体温の」もよかった。

booktalkradio.seesaa.net

しろいろの街の、その骨の体温の (朝日文庫)

しろいろの街の、その骨の体温の (朝日文庫)

これは単純に言うとスクールカーストの話で、主人公の女の子が進学していくにつれ徐々に「いけてない女子」としてスクールカーストを下っていくところ、ヒーロー(男主人公の恋人役が「ヒロイン」なら女主人公の恋人役は「ヒーロー」でいいだろう)が逆にスクールカーストの上位に行ってしまうところ、スクールカースト上位やスクールカースト下位の女子の間でそれぞれ行われるえげつない差別、などなど見所満載なのである。

けどこれも、実際にスクールカーストの洗礼を浴びた女性にとってはいまさら小説で読んで面白いものではないかもしれない。僕自身は中学・高校といわゆる「オタクグループ」に属して人畜無害の存在として扱われていたのだが、女子の間ではこのような闘争が行われていたのだろうか。地方だと親の間の経済格差もそれほど大きくないし、都市部のそれよりは大したことないのではないかと思うのだが。

奥さんに聞いたところ現代の東京にはスクールカーストだけでなく「スクール間SNSカースト」も存在するらしく、例えばプチ金持ち以上の子が通う高校にはInstagramで何か投稿すれば「いいね!」が沢山つく女子高生が量産されている一方で普通の高校ではInstagramよりTwitterが流行っている…(当たり前だが、Instagramで写真を取って何か自慢するにも「カネ」がいるのだ。せちがらい話だ。西原理恵子だ。)というスクール間カーストとSNSを合わせて煮込んだ地獄のような状況を耳にしてしまって、ああ僕は地方に男として、SNSがない時代に生まれてよかった…と胸をなで下ろすことにもなったのだった。(了)

PostgreSQLのLISTEN/NOTIFYでJSONデータを飛ばす

PostgreSQLではJSON, JSONBデータ型のサポートがあります。MySQLも5.7からあるようです。

アプリに対してちょっとした通知を飛ばしたくなったのですが、ちょっとした通知のために別のミドルウェアを導入するのは明らかに筋が悪いのでPostgresのLISTEN/NOTIFYを使うことにしました。

PostgresのLISTEN/NOTIFYはtext型しか渡せないので、ここは構造化されたデータををJSONなどで渡したくなるのが人情というものです。

以下のようなTRIGGERと

DROP TRIGGER IF EXISTS my_table_inserted ON my_table;

CREATE TRIGGER my_table_inserted
    AFTER INSERT ON my_table
    FOR EACH ROW
    EXECUTE PROCEDURE on_my_table_inserted();

以下のようなFUNCTIONで人情を達成します。

CREATE OR REPLACE FUNCTION on_my_table_inserted() RETURNS trigger AS $$
BEGIN
    PERFORM pg_notify('my_table_inserted', json_build_object(
        'id', NEW.id,
        'data_1', NEW.data_1,
        'data_2', NEW.data_2
    )::text);
    RETURN NEW;
END;
$$ LANGUAGE 'plpgsql';

結局のところ送っているのはtext型なのですが、受け取る側でJSON decodeすれば問題ないです。

Pythonで非同期に通知を受け取るコードは別記事に書きます。

完全に余談ですが、現状のPipelineDBではTRIGGERとLISTEN/NOTIFYをこのように組み合わせてCONTINUOUS VIEWの更新通知を受け取ることは出来ません。 CONTINUOUS TRIGGERの実現方法が原因だと思うのですが、多分そのうち何とかなると思います。

PostgreSQLちょっといい話

最近、PostgreSQL(というかPipelineDB)をいじる機会があったのでメモ。

RDBMSにあるとうれしい、というかPostgresにあって「これ良いな」と思った機能を適当に列挙するの回。

CREATE DOMAIN

要はtypedefが出来る。

CREATE DOMAIN company_code smallint;

これで型の別名を定義して、table定義でこの別名を使うことにより

  • 開発中に型の定義を「やっぱこっちで」と切り替えるのが簡単になる
  • 本来同じ型の値を使うべきtable間でうっかり定義がずれてしまうことを抑止できる

などなどのメリットが得られる。地味な機能だけど割とうれしい。

Lateral Join

Reuse Calculations in the Same Query with Lateral Joins

くやしくはリンク先を読んでほしいのだけど、要はカラムの値を元にした計算結果を使って更に何らかの値を計算したいような場合に、まあまあ簡便な記法でクエリが書けるということ。 集計とかでうれしい。

RETURNING

MySQLだとlast_insert_idとかを使うしかないが、RETURNINGを使うとINSERTやUPDATEした後に任意の値を返せる。よい。

思ったよりネタがなかった。

Pythonで async def / def 両対応のデコレータを書く

タイトルの通り。

単純に以下の様なdecoratorを書くと、async defをラップできない。

from functools import wraps
from datetime import datetime

def timetrack(func):
    @wraps(func)
    def inner(self, *args, **kwargs):
        start = datetime.now()
        return_value = func(self, *args, **kwargs)
        end = datetime.now()
        self.logger.info('%s takes %s sec', func.__name__, (end - start).total_seconds())
        return return_value
    return inner

かといって次のように書くと、今度は普通のdefが勝手にcoroutine functionになってしまって困る。

from functools import wraps
from datetime import datetime

def timetrack(func):
    @wraps(func)
    async def inner(self, *args, **kwargs):
        start = datetime.now()
        return_value = await func(self, *args, **kwargs)
        end = datetime.now()
        self.logger.info('%s takes %s sec', func.__name__, (end - start).total_seconds())
        return return_value
    return inner

なので、asyncio.iscoroutinefunctionを使って以下のように分岐するといける。

from functools import wraps
from datetime import datetime

def timetrack(func):
    if asyncio.iscoroutinefunction(func):
        @wraps(func)
        async def async_inner(self, *args, **kwargs):
            start = datetime.now()
            return_value = await func(self, *args, **kwargs)
            end = datetime.now()
            self.logger.info('%s takes %s sec', func.__name__, (end - start).total_seconds())
            return return_value
        return async_inner
    else:
        @wraps(func)
        def inner(self, *args, **kwargs):
            start = datetime.now()
            return_value = func(self, *args, **kwargs)
            end = datetime.now()
            self.logger.info('%s takes %s sec', func.__name__, (end - start).total_seconds())
            return return_value
        return inner

一般化したユーティリティみたいのを書けないこともない気がするけど、今回そこまでのモチベーションはなし。

Pythonのasyncioで(結果を待たない非同期実行|投げっぱなしジャーマン|fire and forget)する

Pythonは3.5から、C#みたいなasync/awaitを用いた非同期処理が可能になっている。

これ系のパターンでは「非同期処理を待つ」ためにawaitキーワードを使い、「(awaitを含む) 非同期処理が行われる関数をマークする」ためにasyncキーワードを使う。非同期処理の終了は行儀よくawaitで待ち受けるのが普通だが、時には単に非同期処理を投げっぱなしにしたいこともある。ログを書くとか、通知を飛ばすとか。

C#においてはasyncかつ返り値をvoidにすればawaitせずにコールできる非同期メソッドが書ける(逆に言うと、awaitする必要がある非同期メソッドはTaskかTaskを返すということになっている)のだが、pythonだとぱっと見どうすれば同じことが出来るのか分からない。その上、われわれ日本人にはどういう単語で検索すればいいかよくわからない。

知ってる人は「fire and forget」とかで検索すればいいのだが、これは一度どこかで見て覚えていなければどうしようもない類の知識なので、わざわざ日本語で検索しやすいようにとこんな記事を書いているのだ、という長い前置き。

次のようにすればオッケー。(以下のコードはそのままコピペして動くものではなく、あくまで「例」)

import asyncio

async def hogehoge():
     pass

asyncio.ensure_future(hogehoge())

使いようによってはensure_futureには落とし穴があったりするんだけど、それはまた別の機会に。