RubyKaigi 2019 "A Type-level Ruby Interpreter for Testing and Understanding" の発表要旨

こんにちは、クックパッドで仕事で Ruby の開発をしている遠藤(@mametter)です。もうすぐ RubyKaigi ですね! クックパッドはいろんな形で RubyKaigi に参加していく予定なのでよろしくお願いします。詳しくは昨日の記事をごらんください。

さて、そういうわけで RubyKaigi です。遠藤は "A Type-level Ruby Interpreter for Testing and Understanding" という発表を予定しています。遠藤の発表予定の内容をあらかじめざっと紹介してみます。

この記事は発表資料を作り終えてから書いているのですが、発表資料よりも要点がまとまっている気がします。

はじめに: Ruby 3の静的解析

2020 年にリリースが予定されている Ruby 3 は、「静的解析」「高速化」「並列性」の 3 つを備えることを目標に掲げています。この発表は 1 つめの「静的解析」に関わるものです。

Ruby 3 に向けた型システムとして、SteepSorbet が提案されていますが、いずれもメソッドの型はユーザが指定する前提になっています。

本発表では、「型は絶対に書きたくないでござる」の人たちのために、型注釈がない Ruby プログラムに適用可能な静的型解析器、「型プロファイラ」を提案します。

型プロファイラとは

型プロファイラは、

  • 型注釈がない素の Ruby プログラムを入力して、
  • 型エラーの可能性を警告したり(Testing)、
  • 型シグネチャのプロトタイプを生成したり(Understanding)

できるツールです。

Testing の例

型エラーを警告する例を示します。

def foo(n)
  if n < 10
    n.timees {|x| # TYPO!
    }
  end
end

foo(42)

このプログラムは Integer#times を typo して timees と書いています。このプログラムに型プロファイラを適用すると、次のような出力が得られます。

$ ./run.sh /tmp/test.rb
/tmp/test.rb:3: [error] undefined method: Integer#timees
Object#foo :: (Integer) -> (NilClass | any)

Integer#timees は undefined である、というエラーが出ています。なお、元のプログラムを普通に実行するだけではこのバグを検知できないことに注意してください(n < 10 なので)。

Understanding の例

次は型シグネチャのプロトタイプを得る例です。

def foo(n)
  n.to_s
end

foo(42)
foo("STR")
foo(:sym)

このプログラムに型プロファイラを適用すると、次のような出力が得られます。

$ ./run.sh /tmp/test.rb
Object#foo :: (Integer) -> String
Object#foo :: (String) -> String
Object#foo :: (Symbol) -> String

foo はオーバーロードされていて、

  • Integer を受け取ったら String を返す
  • String を受け取っても String を返す
  • Symbol を受け取っても String を返す

ということを表現する型シグネチャのプロトタイプとして使えます。

これが型プロファイラの基本です。

もっとデモがみたい!

このへんにいろいろ転がってます。詳しくは発表で。

https://github.com/mame/ruby-type-profiler/tree/master/smoke

型プロファイラをどのように使うか?

おおまかに 2 つの使い方を想定しています。

  1. 開発中にテストと合わせて実行し、型エラーの可能性を調べてみる
  2. Ruby プログラムから型シグネチャをプロトタイプし、手修正の上で型検査器(Steep や Sorbet)を使ってきちんと検証する

前者の使い方は、従来の Ruby のプログラミング体験にあまり影響を与えず、静的解析を補助的なテストとして利用する方法です。 推定される型シグネチャは特に利用しないか、参考程度にします。

後者の使い方は、型シグネチャの生成支援です。 型シグネチャをあとからまとめて書きたい場合、特に既存の Ruby プログラムに対して型検査器を適用する際に役立つと思います。 また、よくわからない Ruby プロジェクトをいじらないと行けないとき、プログラムの中にどのようなクラス・メソッド定義があるかを俯瞰するためにも有用かもしれません。

型プロファイラのメリット・デメリットは?

メリットはただ 1 点に集約されます。

  • 型注釈がなくても型検査・型推論っぽいことができる

デメリットはいろいろあります。

  • 誤った警告(false positive)を出すことがある
  • 各メソッドを起動する型レベルのテストが必要、足りないとバグの見逃しにつながる
  • 原理的に扱えない Ruby の言語機能がある(たとえば Object#send や特異クラス)
  • スケーラビリティに問題がある

長くなるのでこの記事では説明を省きますが、発表ではこれらの問題の分析や、それらに対して何ができると考えているかについて駆け足で語ります。

型プロファイラはどのように動いているか?

今回のメインコンテンツです。型レベルで Ruby プログラムを解釈実行するインタプリタがコアになっています。

def foo(n)
  n.to_s
end

foo(42)

というコードがあったとき、普通のインタプリタであれば

  1. 関数 foo に整数 42 を渡して呼び出す
  2. 関数 foo の中を n = 42 の環境で評価する
  3. n.to_s を実行した結果の文字列 "42" を関数 foo がリターンする
  4. foo(42) の呼び出しが "42" を返して実行再開する

というように実行が進んで行きます。型プロファイラはこれを型レベルで行います。つまり、

  1. 関数 foo に整数 Integer を渡して呼び出す
  2. 関数 foo の中を n :: Integer の環境で評価する
  3. n.to_s を実行した結果の String を関数 foo がリターンする
  4. foo(42) の呼び出しが String を返して実行再開する

このような型レベルでの実行を記録し、各関数に渡された引数(Integer)や返した返り値(String)を集めて、型シグネチャのような形式にして出力します。

分岐があったらどうするか?

関数に型レベルの情報しか渡さないので、分岐の条件を正確に評価できなくなります。たとえば次の例。

def foo(n)
  if n < 10
    n
  else
    "string"
  end
end

foo(42)

n < 10 という条件式がありますが、n には 42 という具体的な値ではなく Integer という型レベルの情報しか入っていないので、分岐を正確に実行することはできません。

型プロファイラは、分岐があったら状態をフォークします。つまり、true の可能性と false の可能性を両方とも実行します。上の例で言うと、true のケースは n (Integer) をリターンする、false のケースは "string" (String) をリターンする、ということで、これらを組み合わせて

$ ./run.sh /tmp/test.rb
Object#foo :: (Integer) -> (String | Integer)

というシグネチャを生成して出力します。

このフォークのせいで、うまくないコードを書くと状態爆発につながってしまいます。通常のコードで状態爆発が起きにくいように抽象化の粒度や状態管理をうまくやるのが、型プロファイラの設計のむずかしいところです。

この手法は何なのか?

普通の型システムとはいろいろ異なると思います。普通の型システムは、メソッドなどのモジュール単位で検査できるよう、メソッド間をまたがない(intra-procedural な)解析になるように設計されます。この点、型プロファイラはメソッド呼び出しがあったときにメソッド本体を呼び出すので、メソッドをまたがる(inter-procedural な)解析になっています。

型プロファイラの手法を指すぴったりの技術名は調べてもわかりませんでしたが、どちらかというと、抽象解釈や記号実行といった技術に近いようです。

なお、inter-procedural な解析は、先に問題として述べたとおり、スケーラビリティとの戦いになりやすく、型プロファイラも例外ではありません。発表ではどのように対策してきたか、対策していきたいと考えているかを議論します。

型プロファイラの完成度は?

発表で詳しくいいますが、端的に言えば、残念ながらまだまだ完成度が低いです。ソースコードは mame/ruby-type-profiler に公開してありますが、正直に言って、まだまだみなさんのコードに適用を試せる段階にはないです。スケーラビリティのための根本的な対策検討から、地道な組み込み機能のサポートまで、やることがたくさんあって手が回っていません。型を書かない Ruby 体験を維持したいと思っている方は協力をご検討いただけると嬉しいです。

まとめ

本発表では、型注釈がない Ruby プログラムに適用可能な静的型解析器、「型プロファイラ」を提案します。 抽象解釈の考え方に基づいていて、現在のところ型なし Ruby 体験を維持できそうな静的解析アプローチの唯一の提案になっています。

発表では、ここまでに実装できている機能のデモや、現状の問題点の解説、preliminary ながら評価実験などをいろいろご紹介したいと思ってます。 ぜひ聞いていただいて、前向きに興味を持ってくれた方とも批判的な立場の方ともいろいろ議論できることを楽しみにしています。

クックパッド一同は、RubyKaigi 2019でみなさんにお会いできることを楽しみにしています!

こんにちは! 広報部のとくなり餃子大好き( id:tokunarigyozadaisuki )です。

先月公開した【RubyKaigi 2019 参加者に捧ぐ】福岡で起業した男が本気で書いた福岡グルメまとめ は見ていただけましたでしょうか? 私は、ブログへの掲載はありませんでしたが、福岡の餃子には牛肉をタネに使っているお店があるとの情報を入手したので、食べに行くぞと意気込んでいます。

クックパッド株式会社は、2019年4月18日(木)〜20日(土)に福岡にて開催される RubyKaigi 2019に、Ruby Committers’ SponsorとWi-Fi Sponsorとして協賛します。

Wi-Fi Sponsorに関しては、調達・設計・構築・運用などを、昨年に引き続き @sorah が担当しております。

クックパッドに所属する4名が登壇し、 @asonas@sorah が運営として関わってくれています。

本ブログでは、登壇する社員のセッションのスケジュールや、ブースで行う登壇者へのQ&Aタイム、Cookpad Daily Ruby Puzzles など限定企画の紹介をいたします。RubyKaigi 2019に参加する弊社メンバーは内定者を含め約30名! みなさまと交流することを楽しみにしています。

参加社員一覧

@mirakui, @tapster, @kanny, @takai, @hogelog, @l15n, @ko1, @mame, @hokaccha, @eisuke, @giga811, @riseshia, @inohiro, @ukstudio, @hfm, @davidstosik, @aadityataoaria, @sikachu, @asonas, @sorah, @pndcat, @sankichi92, @kojitaniguchi, @to9nariyui

登壇スケジュール

はじめに、社員が登壇するセッションのスケジュールを紹介します。

1日目 4月18日(木)

  • 14:20-15:00 笹田耕一(@ko1): Write a Ruby interpreter in Ruby for Ruby 3
    本発表では、RubyインタプリタをRubyで記述するために必要となる要素技術についてご紹介します。Rubyで、といっても、コア部分は相変わらずCで書く必要がありますが、組込メソッドをなるべくRubyで置き換えていきたいという話になります。ここで、課題になるのは、(1) Ruby だと呼び出しが遅いかもしれない (2) Ruby だと読み込みが遅いかも知れない、の二つです。本発表では、これらをどのように解決するかについて議論します。
  • 15:40-16:20 遠藤侑介(@mame): A Type-level Ruby Interpreter for Testing and Understanding
    Ruby 3の静的解析技術の1案として、Rubyプログラムを型レベルで仮想的に実行するRuby処理系を提案します。すでに提案されている他の静的解析と異なり、型注釈がなくてもなんとなく検査・推定ができるところが特徴です。仮想的な実行の過程で発見された型エラーの可能性や、メソッド呼び出しの引数や返り値の型を報告することで、ユーザのテストやプログラム理解を支援することを目指しています。本発表では、詳しいアイデアと、他手法との比較、現在どこまで実装できているかなどを説明します。

2日目 4月19日(金)

  • 17:20-18:20 ライトニングトーク
    • 井上寛之(@inohiro): Write ETL or ELT data processing jobs with bricolage.

3日目 4月20日(土)

  • 10:00-11:10
    • Cookpad Presents: Ruby Committers vs the World こちらのお時間では、Cookpad Ltd CTOの Miles Woodroffe がご挨拶いたします。また、笹田耕一と遠藤侑介が司会を務めます。
  • 11:20-12:00 Sangyong Sim(@riseshia): Cleaning up a huge ruby application
    cookpad.com を支える巨大なレポジトリから未使用コードの削除を進めています。この作業は比較的コストが高い割にリターンが見えづらいです。この問題をどういった仕組みで解決しようとしているのかについて、お話しします。 Ruby 2.6 で導入された Oneshot coverage などを利用し、本番で実行されたコードを記録する仕組みも紹介します。

ブース

RubyKaigi 2019にて出展するクックパッドブースでは、福岡県にゆかりがある料理の限定レシピ集をお配りするほか、 @mame@ko1 が考案した Cookpad Daily Ruby Puzzles を一日3問ずつ公開いたします。各問題に最小数の文字を追加し、 "Hello world" を出力してください。

# example
def foo
  "Hello world" if
    false
end

puts foo

早く回答できた方にはCookpad Pad RubyKaigi 2019 Edition など、数量限定で特別なプレゼントをご用意しています。みなさんの挑戦お待ちしております! また、下記スケジュールの通り、登壇者へのQ&Aタイムなどを予定しております。グッズの配布や昨年好評だった豆つかみもバージョンアップして行いますので、ぜひお立ち寄りくださいね。

f:id:tokunarigyozadaisuki:20190415112930j:plain

1日目 4月18日(木)

  • 15:10-15:40 【ブースイベント】午後休憩: Q&Aタイム by @ko1
    この時間は、クックパッドブースに @ko1 がおりますので、1日目 14:20-15:00 Write a Ruby interpreter in Ruby for Ruby 3 に関するご質問がある方は、ぜひこの時間にブースにて、本人に聞いてみてください。

2日目 4月19日(金)

  • 12:30-13:00 【ブースイベント】ランチ休憩: Q&Aタイム by @mame
    この時間は、クックパッドブースに @mame がおりますので、1日目 15:40-16:20 A Type-level Ruby Interpreter for Testing and Understanding に関するご質問がある方は、ぜひこの時間にブースにて、本人に聞いてみてください。
  • 15:00-15:40 【ブースイベント】午後休憩: クックパッド子会社 ウミーベ株式会社タイム
    昨年、クックパッドグループに加わったウミーベ株式会社。ウミーベのオフィスはRubyKaigi 2019の開催地、福岡県福岡市の海辺にあります。本時間には、ウミーベCTO @Nia がブースにおりますので、ウミーベのサービスについてご質問がある方、福岡で働くことに興味がある方はぜひお気軽に話しを聞いてみてください。

3日目 4月20日(土)

  • 12:30-13:00 【ブースイベント】ランチ休憩:Q&Aタイム by @riseshia
    この時間は、クックパッドブースに @riseshia がおりますので、3日目 11:20-12:00 Find out potential dead codes from diff に関するご質問がある方は、ぜひこの時間にブースにて、本人に聞いてみてください。

  • 15:10-15:40 【ブースイベント】午後休憩:Cookpad Daily Ruby Puzzlesの解説
    @mame より、ブースにて三日間に渡って出題した全9問の Cookpad Daily Ruby Puzzles の解説を行います。解けた方も解けなかった方も、考案者からの解説を聞いてスッキリしてください! 

おわりに

クックパッドでは、料理で世界に挑戦する仲間を探しています。クックパッドで働くことにご興味のある方は、お気軽にブースにお越しください。また、会場でクックパッド社員をお見かけの際には、お声がけいただけますと嬉しいです! みなさまにお会いできることを社員一同楽しみにしております。

新規事業のIoTプロダクト開発に必要なこと【連載:クックパッドマート開発の裏側 vol.5】

クックパッド 買物事業部の篠原 @shanonim です。社内の新規事業「クックパッドマート」でエンジニアをやっています。

このエントリは、連載シリーズ【連載:クックパッドマート開発の裏側】の第5回目です。本日が最終回となります。 以前のエントリはこちらからご参照ください。

今回はクックパッドマートのIoTプロダクト開発について、開発の概要、これまでの歴史、そしてプロダクトのこれからについてご紹介したいと思います。

このエントリに書いてあること

  • クックパッドマートについて
  • IoTプロダクトの開発経緯
  • 開発の歴史
  • 開発を通して得た学び

このエントリで紹介しないこと

  • IoTプロダクトを構成する個々のデバイスに関する詳しい説明

クックパッドマートの仕組み

クックパッドマートは、生鮮食品に特化したECサービスです。 商品を自宅に直接届けるのではなく、マートステーションと呼ばれる生鮮宅配ロッカー(冷蔵庫)に商品を配送します。
マートステーションは街の様々な場所に設置されており、ユーザーは自分の注文した商品を自分で取りに行くことができます。

f:id:shanonim:20190412174027j:plain
クックパッドマートの仕組み

クックパッドマートのIoTプロダクト

現在、クックパッドマートには大きく2つのIoTプロダクトがあります。

一つは、上図「② 商品の準備」に必要なラベルプリンターです。 こちらについては、2019/4/10に投稿された @imashin_ の記事に詳しい説明があります。

techlife.cookpad.com

もう一つは、上図「④ 商品の受け取り」に関連するスマートロックです。 クックパッドマートのアプリからマートステーションの鍵を操作することで、商品を受け取るユーザーだけがマートステーションにアクセスできる仕組みを作っています。

f:id:shanonim:20190412174146j:plain:w400

このエントリでは、スマートロック開発についてご紹介します。

スマートロックの必要性

マートステーションには大きく、

  • 有人ステーション
  • 無人ステーション

の2つの形式があります。
前者は、街なかのドラッグストアや酒店・カラオケ店など、有人店舗の店内に設置されています。現在オープンしているマートステーションはすべて有人ステーションです。

これに加えて、よりユーザーにとって便利な選択肢を増やすために計画しているのが無人ステーションです。

  • 例えば、駅の構内に無人ステーションがあれば、帰宅途中に最寄り駅で生鮮食品をピックアップすることができます。
  • 例えば、マンションの共用部に無人ステーションがあれば、建物の外に出なくても食品を受け取ることができます。

無人ステーションが解決しなければいけない問題の一つに、セキュリティ問題があります。 有人監視のない無人ステーションの場合、悪意ある第三者による商品へのいたずらや盗難といったリスクを否定できません。「特定の人だけがマートステーションにアクセスできる」仕組みが必要です。

そこで、無人ステーションの本格的展開に先駆けて、マートステーション向けのスマートロックを開発することになりました。

開発の方向性

初めからスマートロック付きの冷蔵庫を買ってきて導入できれば話は早いのですが、私たちのニーズにマッチした商品はなかなか見つかりませんでした。 そこで、既存の冷蔵庫を改修してスマートロックを開発することにしました。

f:id:shanonim:20190412174328j:plain
スマートロック実装前のマートステーション

改修と言っても勝手に冷蔵庫を分解して改造することはできません。
今回の開発は冷蔵庫を分解・改造せずに、スマートロックの機構だけを外付け実装するという条件で進めています。

スマートロックの仕組み

鍵の仕組みと一言で言っても、世の中には様々な方法が存在します。物理鍵を鍵穴に差し込んで回すもの、ダイヤル式の番号を合わせるもの、カードをかざして開け閉めするもの...
マートステーションに必要な鍵の条件は、次の2つでした。

  • 遠隔で操作できる仕組みが作れること
  • 物理的な施錠能力が高いこと

条件に合う仕組みを探した結果、電磁錠に辿り着きました。

f:id:shanonim:20190412174434j:plain

電磁錠は、電磁石の特性を利用した鍵です。金属と電磁石を重ねて設置して通電すると、磁石の力でロックされます。 (通電時施錠型と通電時解錠型がありますが、マートステーションでは前者を使用しています。) 通電状態を遠隔でコントロールできれば、施錠状態をコントロールすることができそうです。
また、物理的な施錠能力も非常に高く、大人が思いっきりドアを引っ張ってもビクともしないくらいの強度があります。

スマートロックは、この電磁錠を中心として開発が進められています。

開発の歴史

現在進行中の開発も加え、これまで5つのプロトタイプを製作してきました。それぞれの世代にはコードネームとして貝の名前がつけられています。(貝の甲羅が開いたり閉じたりする様子が鍵の開け閉めを連想するという @_litmon_ のアイディアです。)

第一世代: シジミ

f:id:shanonim:20190412174717j:plain f:id:shanonim:20190412174642j:plain

鍵の制御用デバイス 鍵の切り替え用デバイス
電磁錠(通電時施錠型) M5Stack Grove - Dry-Reed Relay

冷蔵庫の上部に電磁ロックを取り付けています。アプリからインターネットを介して制御装置(M5Stack)に解錠コマンドを送ると、冷蔵庫の扉を開けることができます。

課題

  • 熱問題: 電磁錠を連続可動させると、本体が熱を持ってしまう問題がありました。機器が壊れるほどの温度ではありませんが、連続稼働に不安があります。
  • 耐久性: 鍵自体の耐久性に問題がありました。鍵がかかっていることを知らずに冷蔵庫の扉を開けたユーザーが鍵を壊してしまう事件がありました。

第二世代: アサリ

f:id:shanonim:20190412174743j:plain f:id:shanonim:20190412174801j:plain

部品構成は第一世代と同じですが、電磁錠の設置場所を冷蔵庫上部から冷蔵庫内部に変更しています。 これにより、前世代の熱問題を解決することができました。

課題

  • 耐久性: 冷蔵庫内部に固定した電磁錠が時折ずれてしまい、うまく鍵が閉まらない事象が頻発しました。

第三世代: ハマグリ

f:id:shanonim:20190412174830j:plain

第三世代では、電磁錠の設置場所が冷蔵庫下部に変更されています。 冷蔵庫の製造メーカーに設置方法を相談したり、スマートロックの先行事例を研究したり、様々な試行錯誤の末にようやく落ち着いた仕様でした。

第四世代: ホタテ

f:id:shanonim:20190412174903j:plain:w400

現在マートステーションの一部で稼働しているスマートロックは、この第四世代です。 この世代では、物理鍵以外のデバイスを刷新しました。

鍵の制御用デバイス 鍵の切り替え用デバイス
電磁錠(通電時ロック型) Androidタブレット スマートプラグ

鍵の制御用デバイスの刷新

これまで鍵の制御に使っていたマイコン(M5Stack)には2つの課題がありました。

  • 安定性: 長時間の連続稼働が難しく、物理的な故障リスクが高い。
  • 視認性: 前面のディスプレイが小さく、鍵がかかっているのかいないのか分かりにくい。

これらを解決するために、マイコンをAndroidタブレットに変更しました。

鍵の切り替え用デバイスの刷新

これまで、物理鍵の制御(電流の制御)は自分で手作りしたデバイスを使っていました。動作的には問題ないのですが、一般的に市販されているデバイスと比べてみると、安全性や耐久性にやや不安が残ります。

f:id:shanonim:20190412174931j:plain:w600
手作りしていた鍵の切り替え用デバイス

そのため、このデバイスを市販のスマートプラグに変更しました。

第五世代: サザエ

第五世代は、現在開発中の次世代プロダクトです。 デバイスの耐久性をより強固にしたり、プロダクトの状態を外部から常時監視できる仕組みを作りたいと思っています。

新規事業のIoTプロダクトの開発に必要なこと

アイディアを片っ端から試す

今回のスマートロック開発では、プロトタイピングのために必要な資材を買って、試して、ダメなら壊す、を高速で繰り返してきました。 日々手探りの連続ですが、思いついたアイディアを片っ端から試していくことが大切です。
思いついた時点では微妙だなーと思うアイディアも、実際に作ってみると案外悪くなかったり、逆にこれでいける!!と思ったアイディアも作ってみるとダメだったりします。
初期の開発においては「雑でもいい、まだプロトタイプなんだから」と割り切ってとにかく手を動かして学びを得ていくのが一番の近道だと思います。

実際に使ってもらう

実際にプロトタイプが形になったら、とにかくいろんな人に使ってもらうことをおすすめします。 これはソフトウェアのサービス開発におけるユーザーインタビューと同じ概念かもしれません。

マートステーションはクックパッド社内にも設置しており、スマートロック開発の際はここを物理ステージング環境として使いました。

f:id:shanonim:20190412175017j:plain

今もユーザー(クックパッド社員)に意見をもらいながら、日々改善を繰り返しています。

まとめ

マートステーションのスマートロック開発を通して得た「IoTプロダクト開発に必要なこと」をご紹介しました。 最初から完璧なIoTプロダクトを作ることはほぼ不可能です。仕様の検討や実装に時間がかかってしまうため、現実的ではありません。
必要なのは、「次世代版までの時間を稼ぐ現世代版を開発する繰り返し」だと思っています。

  • まずは1~10個世代を作る
  • その世代での学びを、次の10~100個世代の開発に活かす
  • さらにその先の運用に耐える100~1000個世代世代を開発する

現在のマートステーションもまだ完成形ではありません。これからも着実かつ高速に、この繰り返しを続けていきたいと思っています。

cookpad mart Meetup

このエントリでお伝えしきれなかった技術的な話やもっと深い話を、4/24に開催されるミートアップでご紹介する予定です。 cookpad.connpass.com

ご興味のある方はぜひご応募ください!