| @Mac/iPhone

プログラミングをほとんどやらなくなったので iPad Air でよいだろうと思って買ってみたところ、結構使いづらい。

普段 US キーボードを使っているので何の気なしに US 配列の Smart Keyboard Folio ( MU8G2LL/A )を選んだが、日本語と英語の切り替えがハチャメチャにやりづらい。 macOS 標準の日本語入力も ATOK も Ctrl + Shift + j で日本語に、 Ctrl + Shift + ' で英語に切り替えるデフォルトショートカットキーが割り当てられているので、このショートカットで日本語・英語を切り替えていた。 iPad でもこのショートカットで切り替えたいが残念ながら使えない。 JIS キーボードであればおなじみの 英数かな キーがあるのでより簡単に日本語・英語を切り替えられる。

Smart Keyboard Folio のキーピッチの狭さも厳しい。 Apple キーボードぐらいの使い心地を想像していたが、各種キーが小さくなっておりブラインドタッチできない。ふむーという感じ。

Apple のサイトを見ると iPad Pro の 12.9 インチ版はフルキーピッチになってるようなので、プログラミングをしない場合であってもキーボードを使ってそれなりに文章を書くつもりなのであれば iPad Pro 大を選ぶべきであった。

しかし iPad Pro 大と Magic Keyboard の組み合わせとなると ¥172,800 + ¥53,800 = ¥226,600 となり、軽く MacBook Air くらい買えてしまう値段になる。重さ的にも MacBook Air の方が有利そうだ。

iPad Air であれば、コンテンツの消費をメインにしつつ、ある程度はブログ書きもこなせて自分のニーズにマッチするかと期待していたが、正直なところ微妙なようだった。

| @Mac/iPhone

iPad Air (第 5 世代)を買った。

10 年くらい前に第 3 世代の iPad を持っていたが、当時はあまりしっくりこなくてほどなくして使わなくなってしまった。当時の自分のなかの結論としては iPad は様々なアプリからピロピロ通知が来て気になるし、プログラミングできないし、キーボードがないからブログ書きにも使えないし自分にはノートパソコンの方が向いているというのものだった。

しかしプログラミングは卒業してしまったし、今は Apple からカバーと一体となったキーボードも発売されていて、ブログ書きにも使えそうな雰囲気になってきた。 Apple Pencil にも興味がある。そして何より一番気になっている点は iPad 版 Kindle アプリの出来だ。

iOS の Kindle アプリは比較的よくできているのに Mac の Kindle アプリは本当に出来が良くない。クラッシュしまくるし、線を引こうとすると余計なところに引かれてしまうし、使っていてストレスしか感じない。 App Store でのレビューを見ると iPad OS 版の Kindle アプリの評価は悪くないようなので、リストカット感覚で iPad Air を買って iPad 版の Kindle アプリを試してみることにした。

この記事は早速 iPad Air から書いているが、変換に慣れている ATOK を使えないこと以外は特に問題がない。キーボードは一世代前の Smart Keyboard Folio の US 配列( MU8G2LL/A )をフリマサイトで安く購入した。 iPad Air はカメラが一つしかないので現行の iPad Pro 向けに作られた穴が大きい Smart Keyboard Folio よりも一世代前のバージョンの方が向いている。ちゃんと使えるかは reddit で調べて確認した。

これで寝床でゴロゴロしながらブログを書けるようになったので、これまで以上に駄文を濫造していけそうだ。

| @登山/ランニング

阿蘇草原マラニックで走る著者近影

阿蘇草原マラニックというトレランのイベントに参加した。ここ一年間は比較的真面目に走ってるので(靴を一足履き潰した)、山でも走れるのではないかと思って申し込んでみた。ただレースだときつそうなので順位を争わないマラニックというピクニックとマラソンを足し合わせたようなやつを選んだ。

感想としては一人で参加したのにめっちゃ楽しかった。ほとんどの人がグループで来てたのでボッチだとさみしいとは思いはしたが、普段走れない絶景の場所(外輪山の牧野は普段立ち入り禁止)を知らない人たちとはいえ誰かと一緒に走るのは楽しかった。イベント運営の皆さんが醸し出す雰囲気もピースフルでとても良かった。

とはいえコースは結構ハードで、公式の情報で距離 28km で累積獲得標高は 1000m とある通り( Apple Watch のワークアウトログだと距離 29.8km で累積獲得標高は 1143m だった)、普段からそれなりに走っていて、久住くらいなら楽に登れるという人でないと制限時間内にゴールは厳しいと思う。はな阿蘇美からスタートして国造神社で折り返すコースだったが、国造神社のエイドステーション出発の制限時間が2時間50分で、上り500mある14kmの距離を3時間弱で到達するのは歩きでは間に合わず、平地や下りはちゃんと走らないといけない。外輪山は平らなので走りやすいが、阿蘇谷から外輪山に出るまでの登りが結構きつい。もし来年出ようという方はそれなりのトレーニングが必要だと思います。

今回自分は往路は2時間20分、復路は2時間15分(国造神社のエイドステーションで25分くらい休憩)かかってたようだ。往路の最後で足がつり、復路はあまり走れなかった。外輪山に上がったあとはぼちぼちジョギングみたいなペースで走っていたが、外輪山を降りきって樹林帯に入ったところからまた足がつり、最後の 3km くらいは走っては休みを繰り返しながらゴールした。これまで最長でも 10km しか走ったことなく明らかに準備不足だった。前週に叶岳周回 9km を走って足つりとか疲労とかなかったので 20km くらいまでなら余裕だろうと思っていたが折り返し地点手前で足がつったので普段からもうちょい長い距離を走らないとダメそうだ。

トレランは袖なしシャツとかサンバイザーとかハイソックスとかギラギラしたサングラスとかのイメージがあって苦手だったけど、最近はサンバイザーとギラギラサングラスの人は減っていて(袖なしシャツとハイソックスの人はいる)おしゃれな人が多くて華やかだった(女性も多い)。ピークを目指さずトレイルを走るのが目的なのも新鮮で良かった。

参加費は行政の助成を受けているようでわずか1000円で、エイドステーションおよびゴール後の振る舞いや参加賞の温泉入浴券がもらえることを考えるとはちゃめちゃに安い。来年は価格が上がるだろうけど数千円なら全然払う価値あるなと思えるイベントだった。何より阿蘇の外輪山の牧野を走れるのはとても貴重。もし来年も開催されるのなら是非また参加したい。

| @技術/プログラミング

MySQL だけでお手軽に全文検索ができるということを知らなかった。 MySQL 5.6 から入っていたようだった。 Tantivy および Tantiny を使ったやり方を以前記事に書いてサイトで実装しているが、 MeCab によるトークナイズでは二文字の熟語がセットになって四文字になっているようなパターンを取り逃すことがあった(「関連記事」は「関連」と「記事」に分割され、「関連」や「記事」というキーワードで検索したときにはヒットするが「関連記事」で検索するとヒットしない)し、記事追加時の検索インデックス更新処理が不要( MySQL にレコードが追加されたときに勝手に更新される)なので試してみることにした。

やり方は以下の記事を参考にした。

最初にデータベースに全文検索用のインデックスを作成した。

ALTER TABLE `entries` ADD FULLTEXT INDEX index_entry_fulltext(title, body) WITH PARSER ngram;

その後、検索部分のコードを書き換えて以下のようにした。

class Entry < ActiveRecord::Base
  scope :search,
        ->(words) {
          return all if words.blank?
          where('MATCH (entries.title, entries.body) AGAINST (? in BOOLEAN MODE)', words)
        }
end

めっちゃ簡単。

このブログは記事数が 1500 記事くらいなのでぶっちゃけ LIKE 検索でも実用的な速度( 100msec 以内)で結果を取得できるが、 FULLTEXT インデックスを使うと 10msec 程度で結果を取得できる。

ただし Tantivy と比べて劣る点もあって以下は注意が必要。

  1. なぜかわからないが Vim で検索すると何もヒットしない。また Rails で検索すると Rails について触れていない記事もヒットする。 ngram によるインデックスというのはこんなものなのかもしれない。検索ワードが日本語のときはいい感じに結果が表示される。
  2. 複数のテーブルにまたがるデータを一個の検索インデックスにまとめることができない。例えば Tantivy のインデックスは記事のタイトル、本文、カテゴリー、タグをインデックス対象としているが、 MySQL の FULLTEXT インデックスだとテーブルごとにしかインデックスを作れないので(当たり前)、複数のテーブルにまたがる検索をするときにはテーブルを JOIN するしかない。 OR マッパーを使っている場合には利用しづらい。

1 の問題に関しては、 MySQL 5.7 からインデックス生成時の PARSER に MeCab などを指定できるようになったのでそうすると回避できるかもしれない。ただし MeCab のインストールや設定を行う必要があるので要注意。

2 の問題に関しては全文検索システムを入れた方が良さげ。 Tantivy であれば非常に簡単に導入できる。

現状、このサイトでは右上の検索窓から検索したときのインクリメンタルサーチとアーカイブページでの絞り込みは Tantivy を、インクリメンタルサーチの結果で必要な情報が得られなかったときの「全文検索する」と 404 Not Found ページの検索は MySQL の全文検索を使うようにしている。

二つの検索

| @ブログ

404 ページ、昔はそもそもなくて 404 Not Found ステータスを返すだけだったり、あっても「見つかりません」というだけのものが多かったけど、最近はサイトマップ的なコンテンツや代替となるコンテンツを表示するサイトも見かける。というわけでこのサイトでもやってみることにした。

このブログの URL は /YYYY/MM/DD/slug という形式になっている。パスの /YYYY/MM/DD の部分はお飾りで、実際は slug がユニークになっているので slug で表示すべき記事を判定している。

よくあるのが記事を公開後、 slug 部分にタイポを見つけて変更するというケース。しかしすでにその時点で記事が Twitter などでバズってたりすると、 Twitter で共有されている記事を見てやってきた人が 404 Not Found ページを見ることになる(この前の「不便になるインターネット」がまさにそうだった)。それはまずいので slug のタイポを修正すると同時に Nginx の設定ファイルをいじってタイポ修正前の URL から修正後の URL へリダイレクトするようにしていた。しかしリダイレクトごときでサーバーの設定ファイルを修正して root 権限でリロードするというのはめんどい。 SSH でログインもしなければならない。大げさすぎる。

というわけで思いついたのがこの機能で、 Ruby でクラス名やメソッド名をタイポしたときに正しい候補を表示する did_you_mean.gem を利用した。存在しない slug で URL を開くと以下のように候補が表示される。

404 Not Found

コードはこんな感じ。

# Helper
def not_found_candidates
  @not_found_candidates ||=
    begin
      slugs = Entry.published.where.not('slug REGEXP ?', '^[0-9]+$').pluck(:slug)
      spell_checker = DidYouMean::SpellChecker.new(dictionary: slugs)
      current_slug = request.path_info.split('/').last
      slug_candidate = spell_checker.correct(current_slug)
      Entry.published.where(slug: slug_candidate)
    end
end

# View
- if not_found_candidates.any?
    %p Did you mean?
    - not_found_candidates.each do |candidate|
      = link_to candidate.title, candidate.link

データベースから slug 一覧を取り出して辞書とし、 DidYouMean::SpellChecker に食わせて似たページの候補を取得して表示する。タイポありのページを訪れた人はワンクリックしなければならないという手間が増えるが、これでタイポを修正したときに面倒なリダイレクトの設定をする必要がなくなった。

なお 404 ページには検索窓や最近の記事、カテゴリー一覧も表示して回遊性を高めている。

404 Not Found ページ

| @技術/プログラミング

Homebrew で入れた SQLite で load_extension() が動かなくなっていた。

どうも以前は --enable-loadable-extensions でビルドできていたらしいが、 Homebrew 全体でオプション指定をできなくする変更が 4 年前にあったみたいだ。

オプション指定できるのは UX として良くない(ビルド済みのバイナリをダウンロードできない)し、オプション指定を Homebrew のチームでテストしていないからだそう。

TF-IDF で関連記事を表示する機能は SQLite の拡張に依存していたので、最近 Homebrew で入れた SQLite だと機能が動かなくなってしまった(エラーが発生する)。

対策としては自分で SQLite をビルドするしかない。 Mac でそれやるのは面倒なのでこういうのは全部 Docker でやることにした。

Homebrew のウリはビルド済みのバイナリをサクッとダウンロードできてローカルでビルド不要なところではあると思うが、ちょっと凝ったことをしようとするとソースコードをダウンロードしてきて手元でビルドしないといけなくなってしまった。依存パッケージをまとめてインストールできてたことも便利だったんだけど、カスタムインストールをしたいときは昔みたいに依存関係を自分で調べて都度都度インストールしていかないといけなくなった。開発チームの考えもわからなくはないがちょっと残念だ。

ちなみに Homebrew のリポジトリ( homebrew-core )の過去のコミットログを調べるのがめっちゃ大変だった。 tig Formula/sqlite.rb しても 3 分くらい反応がなかった。 git log -Sextension Formula/sqlite.rb してそれっぽいコミットのハッシュ値を見つけて GitHub で検索して何とか上記の Pull Request と Issue に辿り着いた。超巨大なプロジェクトのソースコード管理は Git でやると大変そうだ。

| @WWW

博多駅前

サウナイキタイのサ活と Google マップのクチコミには結構乖離があることに気が付いた。自分が行ってめっちゃ気に入って、サウナイキタイでも好意的なサ活が多い佐賀の KOMOREBI の評価が Google マップでは低い。サウナにも水風呂にも入らないのに物価の安い九州(温泉に 500 円で入れる)で風呂に 1100 円払えと言われたら高すぎると感じる人もいるのだろう。しかも混んでるし。

Google マップのクチコミはとても便利だけど、一つの施設で二つ以上の役務を果たしてる場所や客を選ぶタイプの店は評価が下がると思う。本当は自分の好みにピッタリの施設が低評価となっている可能性がある。なので評価が低いからと切り捨ててしまうのではなく、自分と似た属性の人からのクチコミを探し出して読むと実際のところの雰囲気がわかるかもしれないが、それは難易度が高すぎる。

今日のプラットフォームサービスには不幸なマッチングを回避する仕組みが必要なのだと思う。あなたはサウナ好きだからこの施設は満足できますよとか、ここは常連向けですよ的な情報が簡単にわかるようになるとか。

ただ一方でそれは Instagram で自分のようなおっさんに微エロのリール動画ばかり表示されるようなレコメンデーションとアルゴリズムによる支配がいろんなサービスに広まるということであり、それはそれで残念ではある。

もう一つの選択肢としては、釣り SNS や登山 SNS が出来たように、レビューサイトも専門分化させていくアプローチが考えられる。自分のようなアルゴリズムやレコメンデーションにあらがいたいウェブ縄文人にはそういう進化の方が向いているかもしれない。

というようなことを思ってたら一つ前の記事に言及しつつ伊藤直也さんが似たようなことを書いていた。

2ch みたいにごちゃごちゃカテゴリーがサイドバーに並んでいるのには普通の人には難しすぎるので、釣り掲示板がツリバカメラになり、登山掲示板がヤマレコや YAMAP になったんだろう。考えてみるとサウナイキタイもサウナに特化した SNS ・情報サイトだ。

サウナイキタイに話を戻すと、サウナイキタイはローカルルール満載の町銭湯に対する評価が甘いという特徴もある。サウナイキタイでベタ褒めされてる施設(「番台のおばあちゃんが優しい」、「常連の方達とほのぼの交流」などなど)が Google マップではボロクソに書かれてたりする(「番台は意地悪ばあさんそのもの」、「彫り物を入れたヤクザや半グレの常連でサウナが占有されていて入れない」などなど)。ネガティブなことは書かないというコミュニティポリシーの影響なんだろうか(以前、サ活でドラクエ集団がいたと書いたら公式アカウントから警告された)。サウナイキタイだけ見て福岡の銭湯サウナに行ったら刺青を彫った怖い人から凄まれながら入浴しなければならないという体験をしそうだ。

レコメンデーションにしても専門分化にしても塩梅が難しい。レコメンデーションはやり過ぎるとただの偏見にしかならない( 40 代男性はおっさん → おっさんはエロが好き → 微エロ動画見せとくか)し、専門分化して蛸壺化しすぎると一般的な感覚と離れたレビューが集まるようになってしまう(怖い人だらけの銭湯サウナが高評価)。昔のインターネットではこんなことに悩むことはなかった。

昔のインターネットは良かったとばかり言ってもどうにもならないが、インターネットを飯の種とする者の一人として人が増えたいまのインターネット特有の問題をどうにかしたい。