トレタ開発者ブログ

飲食店向け予約/顧客台帳サービス「トレタ」、モバイルオーダー「トレタO/X」などを運営するトレタの開発メンバーによるブログです。

RubyKaigi 2017に参加してきました

サーバーサイドエンジニアの芹沢です。

以前書いた私のエントリーにて、弊社のRubyKaigi参加者がそれぞれレポートを書くよという話をしましたが、その2人目のレポートです。

tech.toreta.in

私のレポートでは、2日目に行われたセッションから、Progress of Ruby/Numo: Numerical Computing for Rubyを紹介させていただきたいと思います。

スライドはこちら

speakerdeck.com

Ruby/Numo Projectとは

Rubyで機械学習や科学技術計算を行うためのライブラリを開発しているプロジェクトです。
一般に、機械学習界隈ではPythonで実装されたライブラリがよく使われていると言われており、実際私もnotebookやpandasをよく使うのですが、そういったライブラリが解決することをrubyでも解決することを目指しているプロジェクトのようです。*1

Rubyで機械学習を行うための別のアプローチとして、PythonのコードをRubyでも実行可能にするためのbinding機構であるPyCallがあります。今回のRubyKaigiでもセッションとワークショップが行われましたが、こちらはNumpy, pandasといったPythonのライブラリ資産をそのままrubyでも実行できるようにbindingすることを目指しています。

現在複数のgithub repositoryがこのプロジェクト配下に存在しますが、今回のセッションでも説明が多かったNumo::NArrayについて紹介します。

Numo::NArrayとは

多次元配列の計算を高速に行うためのライブラリ(という理解です。。)です。RubyにもArrayやMatricsの標準ライブラリがありますが、用意されているメソッドはPythonの行列計算ライブラリであるNumpyに近いインターフェースとなっています。
numpyとの対応表がwikiにありますが、かなりの割合のnumpyのメソッドを網羅していることが分かります。実際、Numpyの363個ある関数のうち217個をカバーしており、今後さらに91個が追加予定になっているとのことです。

Numo::NArrayを手元で試す

実際にNumo::NArrayを手元で動かしてみました。
Numo::NArrayのgithub repositoryのREADMEにはspecific_installを使う方法が記載されていますが、普通にGemfileを作ってbundle installしてもインストールできるのでこちらを採用しました。

こんな感じのGemfileを書いて bundle install します。

# frozen_string_literal: true
source "https://rubygems.org"

git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }

gem 'numo-narray', git: 'https://github.com/ruby-numo/narray.git'

irbで動かしてみます。

irb(main):001:0> require 'numo/narray'
=> true
irb(main):002:0> a = Numo::NArray[[1,2,3,4,5]]
=> Numo::Int32#shape=[1,5]
[[1, 2, 3, 4, 5]]
irb(main):003:0> a * 2
=> Numo::Int32#shape=[1,3]
[[2, 4, 6, 8, 10]]
irb(main):004:0> a.shape
=> [1, 5]
irb(main):005:0> b = Numo::NArray[[2],[3]]
=> Numo::Int32#shape=[2,1]
[[2],
 [3]]
irb(main):006:0> c = a * b
=> Numo::Int32#shape=[2,5]
[[2, 4, 6, 8, 10],
 [3, 6, 9, 12, 15]]

最後の行でやっている計算はbroadcastingという機能を使って実現されています。

これは、行列同士の四則演算を行う際に、両者の行列の形状が異なる場合であっても、次元が少ない方の配列を拡張して計算できるようにしてくれるものです。

これをrubyのArrayだけでやろうとすると結構複雑なmap処理をしないと実現できませんがNumo::NArrayを使うとシンプルに記述することができます。

また、今回のセッション中でも紹介されていたマスク処理という機能があります。これは配列内の各値に対して、条件にマッチしれいれば1,そうでなければ0の配列を返す機能です。

irb(main):001:0> a = Numo::NArray[1..10]
=> Numo::Int32#shape=[10]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
irb(main):002:0> a < 6
=> Numo::Bit#shape=[10]
[1, 1, 1, 1, 1, 0, 0, 0, 0, 0]

これと同じことをRubyで書こうとすると以下のようになると思います。

 a = (1..10).map { |i| i < 6 ? 1 : 0 }

ステップ関数を配列の全要素に対して適用して二値ベクトルを得る時などに直感的に書けそうな感じがしてこのsyntaxは個人的にはすごく気に入りました。

ですが、パフォーマンスが悪ければ実用は厳しいので、実際にこのマスク処理のベンチマークを取ってArrayのみで実装した場合と比較してみました。

require 'numo/narray'
require 'benchmark'

base = (1...1000_000).to_a
compare = 1000

nbase = Numo::NArray[base]

Benchmark.bm do |r|
  r.report 'narray' do
    (nbase < compare).to_a
  end

  r.report 'map' do
    base.map do |v|
      v < compare ? 1 : 0
    end
  end
end
       user     system      total        real
narray  0.030000   0.010000   0.040000 (  0.029366)
map  0.060000   0.010000   0.070000 (  0.087115)

Numo::Int32のMask処理の方がArrayのmap処理のみで実装したサンプルよりも2倍ほど早い結果が出ました。

ただ、勘のいい人はお気づきのように、これはrubyのArrayオブジェクトをNumo::Int32のオブジェクトに変換する処理をBenchmark対象に入れていない場合の結果です。変換処理もベンチマーク対象に含めてみると

       user     system      total        real
narray  0.140000   0.000000   0.140000 (  0.156649)
map  0.070000   0.010000   0.080000 (  0.077548)

ベンチマーク結果が逆転します。Numo::Int32オブジェクトに変換する処理はかなりオーバーヘッドが大きい処理と考えられます。そのため、オーバーヘッドを無視できるほど同じ配列を何度も使用するようなケースであればともかく、基本的にはパフォーマンス面ではArrayを使った方が有利のようです。

まとめ

簡単ではありますがNumo::NArrayの紹介をさせていただきました。普段Pythonでscriptを書くこともある身としては、RubyでもNumpyのsyntaxで配列操作をできるようになるのはとてもありがたいことなので、projectの今後の発展に期待したいところです。
また、Arrayからのオブジェクト生成時のオーバーヘッドが大きいと本文中に書いたものの、それを無視できるユースケースであればNumo::NArrayの配列計算は相当パフォーマンスが良いと思われるため、うまくハマるユースケースがあれば使っていきたいと思います。

なお、今回のカンファレンスは会社の経費で参加させて頂きました。今年のRubyKaigiのclosingセッションでも「仕事としてRubyKaigiに参加している人はいますか?」というa_matsudaさんの質問に沢山の手が挙がる様子が見受けられましたが、このようなカンファレンスに業務として参加させていただけることはとてもありがたいことだと思います。

最後に、お約束になりますがトレタはサーバーサイドエンジニアを募集しております。来年のRubyKaigiは仙台なので、牛タンと萩の月と笹かまぼこが食べたい方は是非ご応募ください。*2(そうでない方もご応募ください) www.wantedly.com

*1:もし違ったらすみません…

*2:ちなみに今回はお好み焼きを食べ損ねました…

RubyKaigi2017とModule Builder Pattern #rubykaigi

サーバサイドエンジニアの中村です。先日開催されたRubyKaigi2017に参加しました。 その中で最も興味深かったセッションである The Ruby Module Builder Pattern について自分なりの理解をまとめてみようと思います。

Moduleの問題

module内でdefine_methodを使ってメソッド定義を行ったmethodに対して、そのmoduleをextendしたclass内で該当のメソッドに対して super を呼び出すことができません。

module AdderDefiner
  def define_adder(*keys)
    define_method :+ do |other|
      self.class.new(
        *(keys.map { |key| send(key) + other.send(key) })
      )
    end
  end
end

class LineItem < Struct.new(:amount, :tax)
  extend AdderDefiner

  define_adder(:amount, :tax)

  def +(other)
    puts "Enter Adder..."
    super.tap { puts "Exit Adder..." } #=> No Method Error
  end
end

これは、+ methodが define_methodを使ってmodule内 で定義されているため、super で呼び出す祖先Object内で+ methodが定義されていないためだと認識しています。

これを回避するために、Anonymous Moduleを使ってこの祖先Object内で定義済みにするテクニックを今回のセッションで初めて知りました。

module AdderDefiner
  def define_adder(*keys)
    adder = Module.new do
      define_method :+ do |other|
        self.class.new(
          *(keys.map { |key| send(key) + other.send(key) })
        )
      end
    end
    include adder # この時点でAnonymous Moduleをincludeすることでsuperが利用可能
  end
end

このテクニックを応用したパターンがModule Builder Patternです。

The Ruby Module Builder Pattern

発表者の @shioyama さんに Module Builder Pattern を理解するための良いサンプルコードはありますかと質問したところ、 http://dry-rb.org/dry-equalizer というgemのコードがオススメだと教えていただきました。

そこで、本記事では dry-equalizer のコードを例にModule Builder Patternを理解していきたいと思います。

dry-equalizer

dry-equalizer gemはincludeしたclassの以下4つのmethodを再定義します。

Object#inspect
Object#hash
Object#eql?
Object#==

具体的な挙動は Examples のようになります。この挙動を踏まえて実際のコードを見ていきましょう。

Module Builder Patternのポイントは、Module.class #=> Class であることを利用して、動的にModuleを生成している部分です。

class Equalizer < Module

このように、Equalizer classを Module のsubclassとして定義することで、Anonymous Moduleのメリットが利用できます。

次に、included を見ていきます。

def included(descendant)
    super
    descendant.send(:include, Methods)
end

Equalizer が他classにincludeされた際に、

def eql?(other)
    instance_of?(other.class) && cmp?(__method__, other)
end

def ==(other)
   other.is_a?(self.class) && cmp?(__method__, other)
end

をincludeして再定義します。

そして最後に initializeです。

def initialize(*keys)
  @keys = keys
  define_methods
  freeze
end

初期化するkeyを受取り、define_methods を実行し、cmp?, hash, inspectを定義します。

このように、Module Builder Patternを使うと、非常にわかりやすくメソッドを動的に生成することができます。

おわりに

私はAPIのコードを書く上で常に設計についてどうするのがより良いか、チームの人と議論することが多く、今回のModule Builder Patternのような話は良い設計の1つのテーマとして非常に良いお話でした。

http://dry-rb.org というRubyの素晴らしい設計を集めたリポジトリも今回のRubyKaigiに参加するまでは知りませんでした。他のリポジトリのコードからも学びつつ、社内で良い設計とは何かについてまたさらに議論したいと思います。

またありがたいことに、弊社では国内・海外のカンファレンス参加は出張扱いになっており、カンファレンス参加費・交通費・宿泊費(一部)は会社負担になります。業務として色んなカンファレンスに参加したい!という方はぜひご応募お待ちしています。

iOSDC 2017に参加してきました

iOSエンジニアの高です。

去年に引き続き、今年もiOSDCに参加してきました。

iosdc.jp

今回は前回以上に色んな所がパワーアップしていて、運営のみなさんの本気を感じました。 トレタとしてはゴールドスポンサーとしてスポンサードさせていただいています。 バッグの中にお風呂♨️のチラシがありましたよね。あれが弊社です。

またスポンサー読み上げをアークエンジェル艦長のマリューさんにして頂けるとはなんとも嬉しい限りです。
iOSDC Japan 2017 スポンサー紹介 - YouTube

弊社登壇メンバー

弊社からは増井と堀見が登壇しました。

増井はトレタ初のBluetoothデバイスの開発について。 堀見は誰もが悩むログ収集について登壇しました。

f:id:y_koh:20170917133256j:plain 初めて作るIoT機器とBLEの光と闇 | iOSDC Japan 2017

ここで話していた実際の製品はこちらになります。トレタフォン ボックスタイプ

f:id:y_koh:20170917162326j:plain サポート効率を上げるログ収集環境の構築 | iOSDC Japan 2017

気になったセッション

以下、個人的に気になったセッションをいくつか上げていきます。

Build high performance and maintainable UI library | iOSDC Japan 2017
パフォーマンスの話だけでなく、テストしやすいUIコンポーネントの書き方に至るまで幅広い内容でした。 iOSエンジニアであれば誰でも一度はViewControllerになんでもかんでも書いてしまうと思うのですが、テストしやすくするために、データの流れを一方向にして、状態をモデルに分離することが大切とのことです。

アプリエンジニアはどのように事業に貢献すべきか | iOSDC Japan 2017
Fablicのhuinさんの発表。発表を聞いていて非常にトレタと同じ悩みを持っているなと感じました。アプリエンジニアはアプリの使い勝手を良くしたり、ユーザーに喜んでもらえることを一番に考えることが多いと思います。ただ、それだけでは足りなくて、当然ビジネス的な観点も必要になってくるんですよね。

何を持ってユーザーが喜んでいると判断するのか、と質問してみたのですが、定性的にはアプリのレビュー等、直接聴こえてくる声を。定量的にはAnalyticsの結果を元に判断しているとのことでした。

Human Interface Guidelinesから滲み出る限界感を考える | iOSDC Japan 2017
Human Interface Guidelines(HIG)を守れと言うけれど、最近のAppleの標準アプリも守ってないのでは?というところから始まり、HIGとは何なのか。どこまで守ってどこからはみ出してもよいのか?という話でした。

HIGを狩野モデルを用いて解説しているのがとても印象的でした。

触り心地の良いInteractive Transitionをマスターしよう | iOSDC Japan 2017
さわり心地の良いアプリ。アプリエンジニアであれば誰もが目指すところですよね。このセッションではeasing curveに始まり、durationはどれくらいがベストなのかなど、具体的な数値を見せてくれているのがとても貴重でした。

iOSデバイス3,500台を管理する、東急ハンズのデバイス管理手法とは | iOSDC Japan 2017
普段聞く事の少ないMDMのお話。MDMというのは端末を管理するためのもので、管理側でアプリをインストールしたり、端末でできることを制御することが出来ます。普段聞けない話ということもあってとてもおもしろかったです。

業務アプリの切札、Programable Kiosk Mode大全 | iOSDC Japan 2017
こちらもなかなか聞く事の少ない話の1つですね。iPadにはSingle App Modeという1つのアプリしか立ち上げられないモードが有ります。KIOSKスタイルで専用端末や、店頭で展示する場合なんかに使われています。

クラッシュした時の話が面白くて、おそらくOSバグだと思うんですが、Single App Modeでクラッシュするとそのアプリだけでなく、他のアプリも一切起動できなくなるという恐ろしい現象が起きるそうです。一応ワークアラウンドも紹介されていましたが、完璧ではないそうです。

まとめ

今年のiOSDCもとても楽しめました。運営の皆さんには感謝しかありません。 どれも興味のあるセッションばかりで、直接見れなかったものに関してはこれからキャッチアップしていこうと思っています。

今年も素晴らしいiOSDCをありがとうございました。

© Toreta, Inc.

Powered by Hatena Blog