HTC U11 は安心して人におすすめできるシムフリーAndroid端末だ!

www.htc.com

発売日に買って2週間ほど使ってますが、非常に快適です。

ここ1年で Android Z4 ⇢ Huawei P9 Lite ⇢ HTC U11 と変えてきましたが、一番ストレスフリーかもしれないです。シムフリーだし*1、変なビルトインアプリはあまり入ってないし*2、指紋認証がついてるし*3、Felica もついているし*4、USB debugも有効にできます*5。今のところ気になるのは、ボトムナビゲーションのホームボタンが指紋認証デバイスとかぶせているせいで独自実装なのがちょっと慣れないと使いにくいのと、ボトムナビゲーションにあるはずのIME切り替えボタンが存在しないのが不便だということくらいです。

Felicaと指紋認証のついているシムフリーのハイエンドAndroid端末というのがなくて困っていたわけですが、HTC U11は今のところ申し分のない出来です。よかったよかった。

*1:Xperia Z4はいい端末だがシムフリー版を手に入れるのがちょっと難しい。

*2:Huaweiのビルトインアプリは出来が良くなくてトラブルが起きがち。

*3:Xperia Z4は指紋認証が付いていないのが普段使いする上で唯一の欠点だった。

*4:Xperia Z4 はFelica がついている。

*5:Huawei P9 LiteはUSB Debugを有効にできない。

NodeJSのIntlサポートを調べた

  • IntlというのはECMAScript Internationalization APIのこと
  • NodeJSでも利用可能だが、ビルドオプションによって様子が変わる
  • NodeJSのICUサポートは none, system-icu, small-icu, full-icu というオプションが用意されている
    • https://nodejs.org/api/intl.html
    • 公式バイナリのオプションは small-icu で、これは英語だけをサポートしている
    • 起動時にICU dataを与える事も可能
    • この情報は node -e 'console.log(process.config.variables)' で調べられる
  • Intl によってI18N処理をするときに日本語サポートをしたい場合、3つの選択肢がある
    1. full-icu でビルドしたnodejsを使う
    2. small-icu ビルドのままで、NODE_ICU_DATA 環境変数でICU dataを別途与える
    3. intl.js でpolyfillする
  • intl.js はメンテが活発でもなくバグも放置気味
  • 日本のサービスで Intl を使うならfull-icu でビルドしたものを使うのがいいのかもしれない

発表資料: Elasticsearchによる 全文検索の実装

Rails Developers Meetup 2018 (#railsdm) で話した資料です。Railsの話はほとんどなくて、全文検索の仕組みとスコアリングについてのまとめが主です。

Q&Aシステムでの質問もここで回答します。

Q. データの同期はどうされていますか?

同期はActiveRecordのcallbackでActiveJobに更新jobを投げる形で非同期で行っています。また、データ構造などの更新がある場合にindex再構築するときのためのblue-green deployment用のバッチがあります。

Q. 何かgemを使われていますか?使われているなら、どんな選定理由ですか?

いまはelasticsearch-railsを使っていますが、このエントリの後半にあるような理由で捨てようと思っています。移行先はまだ決めていません。

Q. 辞書を作ったりしていますか?

Amazon Elasticsearch Serviceなのでカスタム辞書は使っていません。AESがカスタム辞書に対応したら考えます。

Q. 社内用語のような一般的ではない単語(かつ形態素解析で微妙な位置で区切られてしまう)で検索されるケースが多いとおもいますが、そういうものも、ngramとのハイブリッドで解決していますか?

現在のところ特に工夫はしていません。ngramとのハイブリッドである程度は解決出来ると思っています。

Q. スコアリングの評価について自動化や定量化は行えていますか? 何をもって改善したあるいはその逆であったと言えるか。

現在はスコアリングの評価について自動化や定量化はしていません。現在はユーザーからのフィードバックを元に行っている改善が主です。

ただ定量化自体はすべきだと思っていて、たとえば(私の前職でもある)クックパッド社では「検索成功率」というKPIを定義しており*1、これをもとに検索体験の改善を行っています。Kibelaでも同様にKPIを定義してそれを観測するということは必要だろうと思います。


最初は elasticsearch-rails の使い方の話を中心にしようと思ったのですが、まず全文検索そのものの話を整理しないとそこにたどり着かないなということでその話を中心にしました。

なおKibelaでは現在 elasticsearch-rails を使っていますが、次の理由から別のelasticsearch clientに移行する予定です。

  • elasticsearch-rails はあまりメンテされていない
  • elasticsearch-rails は ActiveRecordのmodel classにフォーカスしすぎていて多くの機能が不要
    • たとえば Model.search というメソッドが定義されるが、基本的に復数modelにまたがった検索しかしないのでこのメソッドを使うことがない
  • elasticsearch-rails から返るモデルが同値比較できなくてテストで困る問題 - Islands in the byte stream で示すようなモンキーパッチがいくつかあって厳しい
    • PR を送ろうにもローカルでテストを走らせるのが難しく、またそもそもあまりメンテされてないっぽいのでやる気がそがれる

じゃあ移行先はどうかというと searchkick はクエリの抽象化が高度すぎてもっと生のelasticsearch queryを使いたいんですよね。GitHubが開発している elastomer-client はわりとよさそうだなと思ってます。

elasticsearch-rails から返るモデルが同値比較できなくてテストで困る問題

elasticsearch-rails はElasticsearchとRails / ActiveRecordのインテグレーションをしてくれるgemです。

こいつを使っているとき、 特定条件下でモデル同士を比較するテストがコケるようになる という現象に悩まされてきました。pryなどで確認しても、たとえば model_a がes-railsからの返却値であるとき、 model_a == Model.find(model_a.id) がコケるのです。これはクラスのリロードがうまくいっていないからではという示唆を得てSpringを切ると、たしかに再現しなくなりました。そこで elasticsearch-rails を調べたところ、 Elasticsearch::Model::Registry がモデルクラスのインスタンスを保持しているようでした。 Springのドキュメントにも触れているとおり、これはNGです。

https://github.com/rails/spring#class-reloading

Rails前提のgemがSpringに配慮したコードになっていないというのはバグといえるかどうか微妙なところですが、とにかく動かないのは困るのでモンキーパッチを当てて凌ぐことにしました。

Monkey Patch

module ElasticsearchMultipleRecordsExtension
  if defined?(::Spring)
    # Workaround for elasticsearch-rails's bug that Elasticsearch::Model::Registry keeps class instances but they are not reloaded on Spring
    ORIGINAL_TYPE_FOR_HIT = Elasticsearch::Model::Adapter::Multiple::Records.instance_method(:__type_for_hit)

    def __type_for_hit(hit)
      type = ORIGINAL_TYPE_FOR_HIT.bind(self).call(hit)
      Object.const_get(type.name)
    end
  end
end

Elasticsearch::Model::Adapter::Multiple::Records.prepend(ElasticsearchMultipleRecordsExtension)

elasticsearch-rails にもpull-requestを送ろうと思ったのですが、bundle installがコケるレベルでメンテされてないので一旦諦め。

Elasticsearchのドキュメントの直し方

Elastic社の @johtani さんに教えてもらったんですが、たとえば Elasticsearchのドキュメント(たとえばFunction Score Query )をみて不足があるなーと思ったとき、Edit リンクからGitHubの該当ページを直接開けるので即pull-reqを作れます。

f:id:gfx:20180228150033p:plain

あとはCLAにサインして数日待つとレビューを経てマージされたりされなかったりします。

実例: [document] s/logarithm/common logarithm/ by gfx · Pull Request #28821 · elastic/elasticsearch

DroidKaigi 2018で『すばらしきGraphQLのSEKAIへようこそ』という発表をしました

DroidKaigi 2018 に参加して発表してきました。

今回はDroidKaigiということでクライアントサイドの視点から解説しています。よって、サーバーサイドの実装についてはまったく触れていません。サーバーサイドの実装については以前発表をしたことがあるのでそちらもどうぞ。

RejectKaigi 2017で「GraphQL on Rails」という発表をしました - Bit Journey's Tech Blog

今回のDroidKaigiはday 1で当日スタッフとして司会もしましたし、個人的にもDroidKaigi Prelude というイベントをするなどして思い出深いkaigiになりました。また来年もできるといいですね。

DroidKaigi PreludeのKPT

やりました。 運営の @okoysm さん、tarappo さん、および会場提供のDeNA様、ありがとうございました。また、一緒に解説していただいた @muumuumuumuu さんもありがとうございました。もう一人の解説者(予定)だったshirajiさんは家庭の事情で参加できず残念でした。インフルエンザが流行しがちな季節は避けたほうが無難かもしれない…。

総じて楽しかったので次に繋げるために一人KTPを残しておきます。

Keep

  • DroidKaigiの前にtimetableをざっと眺める機会があるのはよい
  • 「ニッチそうなセッションを中心に攻める」というのを裏テーマとしてやったのはよかった
    • 非公式ということもありあまり公平性にはこだわらず、好きなように喋れた
    • 「公平性を重視する」みたいにしたらちょっとつらかったと思われる
  • 30分 x 2という時間は丁度よかった
    • 終盤は疲れてきたのでこれ以上時間を書けるのは体力的に無理だったと思われる
  • プレミアムフライデータイム(16:00~19:00)なので家族持ちにはちょうどよかった
  • LT (Rejected LTs + α) もわりと盛り上がったのでよかった

Problem

  • プレミアムフライデータイムなので参加者が限られてしまった
    • 個人的にもすぐ帰らなければならず、懇親もあまりできなくて残念だった
    • プレミアムフライデータイムのミートアップは総じて見ると自分にとってはプラスだけど、マイナス面も無視できない
  • 徹底解説するつもりが、「timetableをネタに適当に喋る」だけだった感はある
    • これについては「徹底解説」と名付けたのがイマイチだったかも
    • それぞれの話のバックグラウンドなどにもちょっと触れたかったが余裕がなかったという反省もある
    • 個人的には、この1年はAndroid開発から離れているので企画自体かなり無理があったともいえる…

Try

  • このミートアップ自体はとてもよいと思うし、来年は自分はやらないと思うけど誰かやってほしい
    • 「DroidKaigiのtimetableの眺めながら1年を振り返る座談会」みたいなのでもいいかもしれない

JavaScript Quiz: (new Array(2)).map($ => 'hello') === ?

まあ、分かってしまえばなんということはないんですが、結構面食らいますね。

MDNにはちゃんと書いてありました。

Array.prototype.map() - JavaScript | MDN

(callback) is not called for missing elements of the array (...) Due to the algorithm defined in the specification if the array which map was called upon is sparse, resulting array will also be sparse keeping same indices blank.

つまり、空要素に対してはcallbackは呼ばれず、配列のsparse状態(空要素があるという情報)も維持されるということなので、仕様どおりの挙動ではあります。

この「空要素(missing element)」はちょっと分かりにくいんですが、要素の置き場所だけが確保されていて実際には何の値も入っていないという要素です。式として評価すると undefined になります。こんなふうに:

console.log( (new Array(2))[0] === undefined ); // true

in 演算子だと空要素かどうかを調べられます。

console.log( 0 in (new Array(2) )); // false
console.log( 0 in [undefined] ); // true

そして Array.from(iterable)[...iterable] はこのsparse状態は保たず、undefined に変換してしまうようです。

console.log(new Array(2)); // [ <2 empty items> ]
console.log([...new Array(2)]); // [ undefined, undefined ]
console.log(Array.from(new Array(2))); // [ undefined, undefined ]

面白いですね!

Ruby 2.5未満でRuby 2.5のStructのkeyword_initを使う

追記: backports v3.11.0の時点ではstruct/new.rbの冒頭に変なputsがあるなど使われている形跡がありません。またフィールド名を予約語にすると使えないという問題もありました。一旦自前のmonkey patchを使うことにしてます。

NEWS-2.5.0 - Documentation for Ruby trunk

Struct.new takes keyword_init: true option to initialize members with keyword arguments. [Feature #11925]

これですね。

こんな感じのモンキーパッチでいけそうです。

# frozen_string_literal: true

# Backport Ruby 2.5's `keyword_init: true`
module Ruby25StructExtension
  module ClassMethods
    def new(*args, keyword_init: false, &block)
      super(*args) do
        if keyword_init
          define_method(:initialize) do |**kwargs|
            super(*members.map { |k| kwargs[k] })
          end
        end

        if block
          class_eval(&block)
        end
      end
    end
  end
end


if RUBY_VERSION < "2.5.0"
  Struct.singleton_class.prepend(Ruby25StructExtension::ClassMethods)
end

実装したあとで、そういえばbackports gemがあったなと思って確認すると実装済でした😇

backports/new.rb at master · marcandre/backports · GitHub

なのでRuby 2.5未満で keyword_init が欲しければ(そして他のRuby 2.5で追加されたメソッドも!)backports gemを使えばいいですね。

IntelliJ IDEAで特定行のGitHub PRを開く"Find Pull Request" pluginがまじイノベーティブ

Kazuho's Weblog: git blameでプルリクエストの番号を表示する

をみて、vscodeとかIntelliJ IDEAのpluginないかな〜と思っていたら、IntelliJ IDEA用にはすでにありました。インストールして数週間使っていますが、git blame を眺める機会が激減していい感じです。ぼくはIDEやeditorにはプラグインを最低限しかいれない派なんですが、これは便利なので "最低限のプラグイン" に入りそうです。

plugins.jetbrains.com

作者は @shiraj_i さんで、ソースコードも公開されてます。

github.com