メドピア開発者ブログ

集合知により医療を再発明しようと邁進しているヘルステックカンパニーのエンジニアブログです。読者に有用な情報発信ができるよう心がけたいので応援のほどよろしくお願いします。

v-onから辿るVueの細道

みなさんこんにちは、フロントエンドピラティストの小宮山です。
しばらく休養していたランニングを再開し、ハムストリングスの探求に勤しんでいるのが近況です。

v-onの不思議

templateでのv-onの書き方にはいくつかバリエーションがあります。
なんとなく書いてもVueがいい感じに解釈してくれてしまうので普段はあまり気にしていないんですが、よくよく考えてみると不思議な挙動をしているようにもみえてきます。

最もオーソドックスなのはこれではないでしょうか。

<input type="text" :value="value" @input="input" />

methodsとして定義しておいた関数をそのままイベントハンドラとしてtemplateに埋め込む形です。

methods: {
  input() { ... }
}

こう書いても動作は同じです。

<input type="text" :value="value" @input="input()" />

見慣れたtemplate構文だと思いますが、@input="~~"~~に書かれた処理はJavaScriptの構文としては全くの別物です。一方は関数の参照であり、もう一方は関数を実行した戻り値であるはずです。
なのにVueのイベントハンドラとしては両者の動作は同じです。

なんででしょう?不思議に思いますよね、思ってください。思ってくれたことにしてこのまま話を続けていきます。

v-onの書き方バリエーション

まずはv-onの書き方を種類分けしていきます。これは特に公式にそういう区分があるわけではなく、勝手に分類してみただけです。

methods埋込み型

<input type="text" :value="value" @input="input" />

オーソドックスな書き方。

methods実行型

<input type="text" :value="value" @input="input('hoge')" />

引数を指定したいときに使う書き方。

③ 関数埋め込み型

<input type="text" :value="value" @input="(val) => $emit('input', val)" />

親コンポーネントにイベントを渡して行きたいときによく使う書き方。

④ 式埋め込み型

<input type="text" :value="value" @input="value = value + 'a'" />

methodsにするのが面倒なときに使う書き方。

おそらくこの4種類が代表的な書き方ではないでしょうか。
例示のために微妙に処理内容を変えてしまいましたが、どの書き方をしてもイベントハンドラとしては期待通りの動作をしてくれるのはみなさん御存知の通りです。

v-onの書き方によるパフォーマンスの違い

4種類それぞれがパフォーマンスに与える影響が気になるところです。

実は先日社内フロントエンド勉強会の場でReact入門が実施され、hook周りの仕組み、特にuseCallbackによるイベントハンドラ最適化の努力にとても興味を惹かれました。useCallbackを使わない場合と比べてコードが冗長になるのは間違いないのに、それを受け入れてまで最適化に不断の努力を行うReactの姿勢には鬼気迫るものがあります。

Reactがここまでやっているんだから、じゃあVueはどうなのよというのは当然の疑問です。実はこの疑問から始まってv-on周りの挙動やコードを調べて回った結果がこの記事だったりします。

本題に戻り、v-onの書き方によるパフォーマンスを検証していきたいと思います。

パフォーマンスの差はありません。

結論がでました、この記事の本題は以上です。
すみません終わりません。ちゃんと根拠を提示する義務を果たします。

v-onのコードを読む

実は当初はパフォーマンスの差があるだろうと決めつけ、ブラウザの開発者ツールでメモリ利用量とにらめっこしたりしていました。
ただどう頑張っても、有意に差があるだろうと見て取れるような状況は発生していませんでした。

そしてv-onの書き方ごとのパフォーマンスグラフを貼り付けて、「こんなにパフォーマンスに差が出る!」「こういう書き方をするVue使いは素人」「これからはこの書き方一択」というマウンティングをかましていくという目論見は見事に崩れ去りました。

f:id:robokomy:20190912140827p:plain
変わり映えしない!

マウンティングは失敗でしたが、なぜ差がでないのかという新たな疑問を抱いてしまうのがエンジニアの性です。差がないと性がかかってしまったなと気にし始めるのもきっとエンジニアの性です。

パフォーマンス差がでない理由をパフォーマンス計測から見つけるのは難しいので、Vueの実装を探索していきます。

以下ではv2.6.10タグのコードベースで紹介していきます。

github.com

はい、見るべきコードはここです。

これが何かというと、@input="~~"~~に書かれた文字列をイベントハンドラとして実行できる関数に変換している部分の実装です。

まずはこの分岐に、先に上げたv-on記法の①と③が突入します。細かい説明は省きますが、handler.valueに上述の~~に書かれた文字列がそのまま入っています。
そのままreturnしているので、イベントハンドラとして~~に書かれた関数がそのまま実行されます。①と③は関数の参照なのでそのまま実行するだけというわけです。

ちなみに$emitしたときはこんな感じでイベントハンドラを実行しています。使う側には魔法に見えても、Vueの内部実装はもちろん魔法ではなく地続きの実装です。

さらに脇道にそれると、thisを付けなくてもtemplate内でmethodsを使えたりするのはここでwith(this)とされているからでした。改めて実装を探ってみると、なるほどなぁという発見がたくさんあります。

(へー便利そうと思っても軽い気持ちでアプリコードにwithを使うのは絶対にやめましょう。)

再び本道に戻ります。

残りの②と④はこちらの分岐で、②だとisFunctionInvocationtrueになり、④だとfalseです。returnされるかという違いはありますが、function($event){ .. }でラップすることで、どちらも~~に書かれた処理がそのまま実行されるのが特徴です。

function($event){ .. }というラップを利用し、イベント引数を$eventという変数として受け取るなんて小技もあったりします。実は今回始めて知りましたが、ちゃんとドキュメントのこのあたりにも書いてあります。

ドキュメントだとネイティブのDOMイベント用っぽいですが、非ネイティブなコンポーネントについても同じイベントハンドラの文字列パースがされているので共通で使うことができます。
イベントをそのまま親コンポーネントに渡したいときのショートハンドとして使えなくもないかもしれません。

<input type="text" :value="value" @input="$emit('input', $event)" />

ただし受け取れるのは第一引数のみです。複数の引数が欲しい場合は素直に(a, b) => hoge(a, b)という形にするか、argumentsを使う必要があります。

@input="hoge(...arguments)"という書き方をするとbabel変換とVueコンパイラの相性が悪いのかエラーになってしまうんですが、この書き方ができる詳しい条件ご存じの方いたら教えて下さい。

v-onの書き方によるパフォーマンス結論

Vueの実装をざっと眺めてもうお気づきだと思いますが、今回紹介した4種類のv-on記法はいずれも同じような加工とパース処理を経て実際に実行されるイベントハンドラへと変換されます。
加工とパースに若干の差異はあるものの、その後の処理に差はありません。

そして加工とパース処理は基本的にビルド時に行ってしまいますので、ランタイムにおいてパフォーマンスに差がでることもないというわけです。function($event){ .. }によるラップで関数呼び出しのネストが増えるのは確かです。とはいえさすがにその差を気にする必要に迫られる環境は皆無だと思います。

今回はv-onについての調査でしたが、render関数を作って直接onの設定をした場合は状況が別物です。この場合はrenderが評価される度にそこに書かれた処理が実行されるので、ReactがuseCallbackで最適化を目指したのと同じような状況が生まれます。

Vueのtemplateを使っている限りv-onのパフォーマンスを気にする必要はない、というよりしても何もできないが正しいですが、renderを使う場面に遭遇したらパフォーマンスについても気にしておくことをおすすめします。

まとめ

v-onを使うときに気にするべきはパフォーマンスではなく、可読性。

もう締めに入っているのに唐突に可読性という主張を始めてしまいました。
templateに複雑な処理を書かないようmethodsに切り出していくことはもちろん重要です。 重要ですが、その理由はパフォーマンス観点から来るものではないというのはこれまで見てきた通りです。

では何の観点かというと、やはり可読性ではないでしょうか。
ただフラグをトグルだけの処理や、ただ親に$emitするだけの処理までmethodsに切り出すべきか判断に迷ったときは是非ともパフォーマンスではなく可読性に重きを置いていきましょう。


(☝︎ ՞ਊ ՞)☝︎是非読者になってください


メドピアでは一緒に働く仲間を募集しています。 ご応募をお待ちしております!

■募集ポジションはこちら

https://medpeer.co.jp/recruit/entry/

■開発環境はこちら

https://medpeer.co.jp/recruit/workplace/development.html

IT x 社会貢献 ~レアジョブ・メドピア 開発事例公開~

こんにちは。サーバーサイドサウナーの川井田(@tamamushi_2)です

先日、レアジョブさんと勉強会を開催し、同期の櫻井(@shibadog39)と登壇してきたので、資料と一言コメントをお送りしますm(_ _)m

medpeer.connpass.com

 

資料・コメント

川井田

私は、Sidekiq Enterpriseの導入事例として、メール配信JOBの改善を例にだして、Enterprise + Pro で使える機能紹介をメインに、発表しました!

機能紹介に力を入れすぎて、何を言いたいのかわからない資料になっていますが、この資料をみてSidekiq Enterprise導入したよって声が聞こえてきたら、嬉しいです。

speakerdeck.com

櫻井

こんにちは。メドピア筋トレ部、幽霊部員の櫻井です。

自分は、WebAPI開発におけるスキーマ駆動開発をテーマにLT登壇させていただきました。 また、今回のイベントのテーマがIT×社会貢献ということで、自分が担当しているサービス「kakari」についても紹介しています。 kakari.medpeer.jp

さて、本題のスキーマ駆動開発ですが、

・API定義のドキュメントをメンテしていくのしんどすぎ
・WebAPI開発の効率を上げていきたい

と思ったことが一度でもある方には、ぴったりの内容となっていますのでぜひ目を通してみてください。

speakerdeck.com

今回のLTでは盛り込めませんでしたが、スキーマ駆動開発を目指してみて「困ったこと、大変だったこと」についても機会があったらいつかお話できればと思っています。

イベントの様子

クラフトビールを用意させて頂き、イベント開始と同時にプシュ!っとして、懇親会も盛り上がりました!

f:id:degwinthegreat:20190902200528j:plainf:id:degwinthegreat:20190901120030j:plainf:id:degwinthegreat:20190901120004j:plain

おわりに

引き続きメドピアでは、開発で得た知見を公開するイベントを予定しております。

メドピアの開発に興味のある方、お酒の飲みたい方は、ぜひ参加して下さい!!


(☝︎ ՞ਊ ՞)☝︎是非読者になってください


メドピアでは一緒に働く仲間を募集しています。 ご応募をお待ちしております!

■募集ポジションはこちら

https://medpeer.co.jp/recruit/entry/

■開発環境はこちら

https://medpeer.co.jp/recruit/workplace/development.html

松本の地でSass/SCSSの邪悪なアンパサンドを撲滅するために立ち上がった

メドピアCTO室フロントエンドエンジニアの小宮山です、よろしくおねがいします。
趣味はボルダリングとヨガとピラティスです、よろしくおねがいします。

6月某日、長野県松本市の地にて開催されたメドピア開発合宿で取り組んだことについて紹介していきたいと思います。

アンパサンドへのウラミツラミ

タイトルにも挙げたとおり、今回立ち向かったのはSass/SCSSのアンパサンド(&)です。 メドピアのリポジトリはほぼSCSSで統一されているので、この記事ではSCSSの記法ベースでコード例を載せていきます。

アンパサンド記法は、BEMライクなセレクタを書くときによく利用されるのではと思います。
例えばこのようなものです。

.header {
  &__foo {
    color: green;
  }
  &--bar {
    color: blue;
  }
}

アンパサンドを使い、冗長な記述を限りなく減らしたスマートな書き方ですね。書いてるときはDRYなライブ感と共にスタイルを書き進めることができます。

しかしこの書き方には、セレクタの検索可能性が損なわれるという非常に大きな欠点があります。

検索妨害

上述のようなスタイル指定がされたコードに手を入れる場面を想定してみます。変更を加えようとしているファイルはhtmlとjs側です。

html

<div class="header__foo">
  foo
</div>

htmlにはheader__fooというclassを持ったタグがあり、jQueryを使ってセレクタからそのタグを取得して何かの操作をしているとします。

js

$('.header__foo').hide()

何の変哲もない古き良きjQueryコードです。

続いてこの処理をVueで置き換えてみます。細かいところは省略しますが、こんな感じになるのではないでしょうか。

<template>

<div class="header__foo" v-show="show">
  foo
</div>

<script>

Vue.extend({
  data() {
    return { show: false }
  }
})

さくっと置き換えることができ、JavaScript側はもうタグに付けておいたclassを必要としなくなりました。
不要なものは消せるときに消してしまうのが正義です。早速header__fooというclassをタグから消してすっきりさせてしまいましょう。

<div v-show="show">
  foo
</div>

と迂闊に消してしまう人はさすがにいないと信じたいです。JavaScriptから参照していなくても、CSSからclassを参照している可能性は大いに残っています。

このclassに対するCSSを書いた本人なら、このclassがまだ必要であることを覚えているかもしれません。しかし人の記憶は頼りになりませんし、このclassを消していいか迷っているのは何も知らない別人かもしれません。消したいclassがまだどこかで使われているかの判断に迷ったときは、grepするに限ります

どんな結末が待っているかはもう想像がついたかと思います。アンパサンドによる文字列結合で指定されているこのセレクタは、タグに付いているclass名でgrepしても拾われません。

.header {
  &__foo {
    color: green;
  }
}

そして使われていないことを確信し、コード改善と思ってclassを消した開発者には、スタイル崩れという悲劇が待ち受けています。

このようにアンパサンドによってセレクタが文字列結合されていると、タグのidやclassがどこで使われているのか見つける手段が大幅に制限されてしまいます。 地道に探せば見つけられるかもしれませんが、その労力と、不要なセレクタを減らす地道なコード改善が釣り合うのかは微妙なところです。
そして結局は触らぬ神に祟りなし、必要なのかも分からないidやclassは消えることなく未来に向かって増え続けていきます。

purgecss

検索妨害と仕組みは同じですが、アンパサンドによるセレクタ結合はpurgecssによる未使用スタイル削除の妨げとなる可能性があります。

purgecssは非常に強力なものの、セレクタを使っているかの判定は静的解析によって為されています。どこまで正確に判定しているのかまでは精査できていませんが、不要なリスクを回避する意味でも、アンパサンドやスクリプトを用いた文字列結合によるセレクタ生成は避けたほうが無難です。

合宿の地、松本にて

grepを阻害する邪悪なアンパサンドによるセレクタ文字列結合は、すでにメドピアのリポジトリ群に多く入り込んでいました。この現状に立ち向かうべく、アンパサンドを使わない素のセレクタ文字列に変換してしまおうと決意を固めます。

そしてメドピアでは年に数回、2泊3日かけて自由なテーマに取り組むことができる開発合宿を開催しています。 ボリューム的にちょうど良さそうだったこともあり、アンパサンド変換用のCLIツール開発をテーマに決めて取り組むことにしました。

採用言語

CLIツールの開発言語にはRustを選びました。フロントエンド関連なのだからJavaScriptやTypeScriptでいいじゃないかという葛藤はもちろんあったものの、舞台はせっかくの開発合宿です。今の最善でないとしても、興味のある技術にフォーカスしてこそ開発合宿です。普段とは違う言語に四苦八苦したくなるときだってあるんです。

成果物

リポジトリはこちら。 github.com

CLI

まずは当初目的通りCLIツールです。clapというCLIツール作成用便利パッケージを使うと引数解析やヘルプ表示などを簡単に作れます。

USAGE:
    sassruist [FLAGS] <path>

FLAGS:
    -f, --fix        fix original file(s)
    -h, --help       Prints help information
    -V, --version    Prints version information

ARGS:
    <path>    target file or directory path

ファイルを書き換えたりといったCLI特有の機能以外はWebAssembly版と同じ動作なので、デモなどはそちらに引き継ぎます。

WebAssembly

フロントエンド、Rustというキーワードが来たら当然続くのはWebAssemblyでしょう。 冒頭で長々と書いた当初の目的を果たすにはCLIツールさえあれば十分ですが、舞台は開発合宿です。隙あらば気になる技術に全力投球していきます。

成果物

netlifyにデモサイトを用意したのでお試しください。 determined-wescoff-282115.netlify.com

妥協点

成果物発表直後ですが早速妥協点紹介です。というのも正直なところ今回の成果物・・・作った本人から見ても実用レベルには至りませんでした、無念。
以下その理由です。

変換結果

「アンパサンドを置換するだけだし実装なんて簡単だろう」と気楽に作り始めてしまったのが運の尽き、Sassアンパサンド(というよりSassそのもの)は想像以上に手強い相手でした。

まずはこの変換例を御覧ください。

f:id:robokomy:20190716135423p:plain
変換例

みにくい!!!

そうなんです。アンパサンドを解決するにはセレクタのネスト構造を解決する必要があり、そのために記述が複雑になることを避けられませんでした。

ちなみに余談ですが、当初は勢いで走りすぎてこういう変換をしていました。間違い探しとしてお収めください。

f:id:robokomy:20190716135917p:plain
間違い探し

制約

100歩譲ってみにくさは我慢するとしても(diffが死ぬので譲りにくいですが)、仕様的に無視しにくい大きな制約もいくつか残ってしまいました。

特に厳しいのは複数行のセレクタに対応できていないことです。

f:id:robokomy:20190716140516p:plain
🤔🤔🤔

見るも無残な姿になってしまいました。「1行ごとに処理すればいいだろう」という謎の自信に満ちた実装方針により、複数行セレクタへの対応が必要と気付いたときには既に軌道修正が間に合いませんでした。

あと地味にSassを諦めてSCSS専用になっています。妥協点満載です。

CLIとWebAssembly両対応ビルド

気を取り直して、今回一番苦戦した点の紹介です。実は実装を差し置いて、CLIとWebAssemblyの両方にRustコードをビルドすることにかなり苦戦しました。
RustやCargo.toml自体への理解度もあまり深くなく、試行錯誤の末になんとか形になった方法をここで紹介したいと思います。

シンプルなWebAssemblyビルド

RustをWebAssembly化し、ウェブページとして公開する1連の流れ自体はこちらの記事を大いに参考とさせていただきました。

webbibouroku.com

bindgen、wasm-pack、create-wasm-appなど、便利なツールの導くままにWebAssemblyなウェブページを作ることができてしまいます。

RustをWebAssemblyにビルドするだけなら解説通りにwasm-packを使うだけです。しかし今回はもう1つのターゲット(こちらが本命のはずなんですが)としてbinaryにもビルドする必要があります。

異なる依存パッケージ

CLIツールとしてはディレクトリ内のファイルを一気に処理させたかったので、dependenciesにwalkdirというファイル探索用パッケージを追加していました。
ウェブページて使うWebAssemblyにファイル操作は不要なのですが、Cargo.tomlのdependenciesに書かれたパッケージがWebAssemblyビルドのときにも認識され、wasm-pack buildコマンドを実行すると以下のようなエラーを吐かれてしまいます。

error[E0433]: failed to resolve: use of undeclared type or module `imp`
   --> /~~~/.cargo/registry/src/github.com-1ecc6299db9ec823/same-file-1.0.4/src/lib.rs:261:9
    |
261 |         imp::Handle::stdout().map(Handle)
    |         ^^^ use of undeclared type or module `imp`

原因は推測ですが、WebAssemblyにビルドできない何か(walkdirなのでおそらくファイル操作系)が混ざっていると判定されているのだと思います。
その証拠に、Cargo.tomlのdependenciesからwalkdirをコメントアウトすると無事にビルドが成功します。

つまり、WebAssemblyにビルドするときだけ手動でその行をコメントアウトすれば解決します!!!

許されません、これは許されません。ビルドコマンドをただ実行するだけなのに、こんなにも露骨な運用でカバープロセスを挟み込むなんて許されるわけがありません。README.mdにこんなビルド方法の解説を書くこともとてもできません。

環境ごとの依存パッケージ変更

本体のコーディング以上に血眼になって探し、ついにここで答えを見つけました。
修正後のCargo.tomlの書き方はこちらです。

gist.github.com

ポイントはclapwalkdirパッケージに付けたoptional = trueというオプションで、featuresとしてbinを指定したとき(cargo build --feautres bin)だけビルド時の依存関係に含めてくれるようになります。

f:id:robokomy:20190705185245p:plain

大まかな流れは上図のようになっています。WebAssemblyとしてビルドするときは不要なパッケージをwasm-pack buildから除外することに成功しました。そしてCLIとしてmain.rscargo buildするときは、--feautres binを引数に加えることで依存パッケージをしっかりと認識してくれます。
引数追加程度ならREADME.mdに書くこともきっと許されることでしょう。

本当はCLIとしてビルドするときにWebAssemblyでしか使わないパッケージも除外したかったのですが、こちらはwasm-packをさらに解析する必要がありそうで今回は泣く泣く断念しました。こちらの問題にも対応した完全版両対応ビルド設定の模索は今後の課題です。

f:id:robokomy:20190705185931p:plain

成果物やデモを公開するには、やはりウェブブラウザで見れるというお手軽さは魅力的です。本命のCLIツールを作り込みつつ、さくっと触れるデモをウェブで公開したいという贅沢な欲求をWebAssemblyは見事に叶えてくれました。

まとめ

Sass/SCSSの邪悪なアンパサンドへのウラミツラミからスタートし、それっぽく動くけど妥協点満載な解決ツールを作り、WebAssemblyの知見を紹介して締めるという全くまとまりのない記事となってしまいましたが、メドピア開発合宿3日間が見事に集約されています。自分で言うのもあれですが見事に集約されています。

実は人生初の開発合宿というイベントでした。好きなテーマに取り組んでいいという我らがCTO福村の言葉をそのまま受け取り、このように本当に好きなことに取り組むことができた濃密な3日間となりました。これで当初目的だったアンパサンド撲滅も達成できていたら最高だったのですが、残念ながら力及ばず。

しかしなんと、同じく合宿に参加していたフロントエンドエンジニア村上(@pipopotamasu)がstylelint-scssのruleでセレクタのアンパサンド結合を禁止するというスマートすぎるPRをさくっと作って本家のルールに加えてくれました。

github.com

邪悪なアンパサンド勢力の拡大はこうして食い止められたのです。松本の地にて立ち上がっていたのは村上です。僕は何もしていません。やはり持つべきものは仲間です。メドピアではフロントエンドエンジニアの仲間を絶賛募集中です。


(☝︎ ՞ਊ ՞)☝︎是非読者になってください


メドピアでは一緒に働く仲間を募集しています。 ご応募をお待ちしております!

■募集ポジションはこちら

https://medpeer.co.jp/recruit/entry/

■開発環境はこちら

https://medpeer.co.jp/recruit/workplace/development.html

開発合宿に行ってきました!@松本

メドピアのサーバーサイドサウナーの川井田です。

メドピアでは、年2~3回のペースで日常業務から離れて、業務改善や、技術研鑽のための開発合宿を開催しており、恒例となっています。

前回の様子はこちら。 tech.medpeer.co.jp

6月26日から28日まで3日間、エンジニア13名で行ってきました!

初めて参加してきたのでレポートしたいと思います。

今回の合宿地は2020年rubykaigiの開催地松本です!

f:id:degwinthegreat:20190703203832j:plain

※我々は遊びに来たのではなく、業務改善や技術研鑽のために合宿に来ております。


開発の様子

ホテルの部屋で集まって開発したり、

f:id:degwinthegreat:20190702082223j:plainf:id:degwinthegreat:20190702082242j:plain

会議室を借りてモブプロしたり、

f:id:degwinthegreat:20190702084618j:plainf:id:degwinthegreat:20190702085548j:plain

早いWi-Fiを求めてカフェに移動したり、

f:id:degwinthegreat:20190702084812j:plain

業務より疲れるという声が聞こえてくるくらいモクモク作業していました。

f:id:degwinthegreat:20190703235502j:plain

※我々は遊びに来たのではなく、業務改善や技術研鑽のために合宿に来ております。


グルメメモリー

ずっとモクモクしていたら心が邪悪になります。

しっかり食事を取ることでピュアな心を取り戻します。

f:id:degwinthegreat:20190702072734j:plainf:id:degwinthegreat:20190703235822j:plainf:id:degwinthegreat:20190704000115j:plain
f:id:degwinthegreat:20190703204341j:plainf:id:degwinthegreat:20190703204944j:plainf:id:degwinthegreat:20190703204351j:plain
f:id:degwinthegreat:20190702072423j:plainf:id:degwinthegreat:20190704000238j:plainf:id:degwinthegreat:20190702072450j:plain

f:id:degwinthegreat:20190704150654j:plain

※我々は遊びに来たのではなく、業務改善や技術研鑽のために合宿に来ております。


20人は収容出来る超巨大ストーブサウナのある銭湯に一人で行くもの f:id:degwinthegreat:20190702082802p:plain

【公式】湯の華銭湯 瑞祥松本|長野県松本インターに近い日帰り温泉

ホテルの宿泊者限定の送迎付き温泉旅館に行くもの

f:id:degwinthegreat:20190704001249j:plainf:id:degwinthegreat:20190704001327j:plain

f:id:degwinthegreat:20190704001337j:plain

www.hotel-shoho.jp

我々...


成果発表

時間も忘れてモクモク作業してきた成果を社内で発表します。 ここに全てをかけています! 外部に公開できそうな成果をまとめます。


開発環境全部構築するまで帰れま10

CTO福村自らみんなの開発環境をカイゼンしていきたい! との思いで取り組んだそうです。

10個のプロジェクトの開発環境を構築し、

立ち上げたコンテナの数はちょうど100!

hostsファイルには48個の*.testが追加されたそうです。

カイゼンPRお待ちしてます。

社員紹介アプリ

急激に社員が増え、顔と名前が一致しないのをカイゼンするために、3人チームで社員紹介アプリを開発していました。

コードは外部に公開されているので、ぜひPRを送ってみて下さい!

github.com

Firebaseを活用した猫監視システム

Firebaseへの好奇心の裏に隠れる愛猫を思う気持ちがとても印象的でした。

reireias-slides.firebaseapp.com

qiita.com

SASS/SCSSのclass名を結合する'&'を撲滅したい

後日詳しいブログを公開予定ですが、SASS/SCSSの'&'を撲滅するためにフロントエンドエンジニア2名が それぞれ違うアプローチで取り組んでいました。

一方はRustでフォーマッターを作ってWebSocketを使ったサーバーレスなリアルタイム変換アプリを公開し、

github.com https://determined-wescoff-282115.netlify.com/

一方は、stylelint-scssにルール追加のPRを作っていました!

※こちらのPRはマージされ、先日リリースされました! https://github.com/kristerkari/stylelint-scss/pull/338

|Added: selector-no-union-class-name rule. github.com


まとめ

私は、普段触らない技術に挑戦し続け、脳内メモリを加速度的に使い切り、 不完全燃焼に終わってしまい、3日間で成果を上げるためには、事前準備が大切であることを学びました。

我々メドピアでは、合宿を始め、輪読会、振り返り会などの、技術研鑽のための取り組みを 日々、行っています。

これらの取り組みを通して、さらに成長して、来年のRubykaigiでまたこの地松本に戻ってきたいと思います!

f:id:degwinthegreat:20190703200647j:plain f:id:degwinthegreat:20190701211105j:plain

(☝︎ ՞ਊ ՞)☝︎是非読者になってください


メドピアでは一緒に働く仲間を募集しています。 ご応募をお待ちしております!

■募集ポジションはこちら

https://medpeer.co.jp/recruit/entry/

■開発環境はこちら

https://medpeer.co.jp/recruit/workplace/development.html

Rails × ECS 運用してみたわかった起動タイプ EC2, Fargate の使い所

メドピアマッスル部上腕二頭筋担当、CTO室 kenzo0107 です。
今回はメドピアの直近のプロジェクトで採用している Rails × ECS Fargate についてです。

直近プロジェクト

直近プロジェクトでは AWS ECS を採用しています。

2018年10月にリリースした スギサポ deli は、メドピアで Fargate 初採用となったプロジェクトです。

スギサポ deli とは?

sugisapo.ws

病気で食事制限が必要な方やシニアの方々、より健康な食生活を目指す方など、誰もが美味しく召し上がれるお食事をお届けするサービスです。

「食事制限」 と聞くと、簡素な食事をイメージされる方もいらっしゃると思いますが
一度見て頂くとお分かりの通り、かなりバラエティに富んだ内容となっており、目にも美味しい品々が並んでおります。

是非一度お試しいただければ幸いです♪

今回お話ししたいこと

以前、 Rails x ECS でオートスケーリング&検証環境の自動構築を執筆しました。

tech.medpeer.co.jp

今回は ECS を運用してきてわかった起動タイプ EC2, Fargate の使い所、また、運用時に有用だったことについて話したいと思います。

ECS についておさらい

まずは ECS の起動タイプ Fargate と EC2 について、軽くおさらいです。

起動タイプ Fargate

インフラレイヤーが抽象化されており、EC2 の管理が不要です。

以下の運用コストがなくなることが何よりも有難いです。

  • EC2 インスタンスの定期メンテ
  • ECS エージェントのバージョン管理
  • EC2 オートスケーリング管理
起動タイプ EC2

EC2 を起動し、その上でコンテナを起動しています。

これらの特性により起動タイプ Fargate, EC2 の使い所を検討しました。

検討事項

  • コンテナへのアクセスはどうやってするの?
  • AutoScaling 速いのどっち?
  • お値段はどう?

コンテナへのアクセスはどうやってするの?

f:id:kenzo0107:20190210235843p:plain

起動タイプ EC2

EC2 へ ssh さえできれば、docker ps でコンテナの起動状態を確認したり、 docker exec でコンテナにアクセスし、 rails console を実行することも可能です。

起動タイプ Fargate

インフラレイヤーが抽象化されている為、サーバへ ssh ログインできません。*1

docker exec でコンテナにアクセスする様なことはできません。

AutoScaling 速いのどっち?

起動タイプ EC2

ECS のバックエンドとして AutoScaling Group で EC2 を起動させ、ECS に紐づけた ALB に EC2 を追加する運用をしています。

その為、 EC2 をスケールアウトさせた後に、タスクをスケールアウトする様にしないと、タスクに偏りが生じる等、正しくタスク配置されない時がありました。

起動タイプ Fargate

タスクのみ考慮すればよいです。

f:id:kenzo0107:20190621221227p:plain

特に、EC2 のスケール分を考慮する必要がないとしても、Fargate のスケールアウトが安定的で速いです。

お値段はどう?

f:id:kenzo0107:20190211002727p:plain
Price

Fargate の価格はタスク数, CPU, Memory に正比例します。Fargate pricing

2019年1月に Fargate の価格が下がったとは言え、タスク数が増えることを考えると、まだ Fargate の方が割高?と思います。

本番・ステージング環境での Fargate, EC2 の使い分け

これまでの起動タイプ Fargate, EC2 の性質を加味して、プロジェクトの性質にも依りますが、以下の様な構成を採用しているケースが多いです。

f:id:kenzo0107:20190619234356p:plain

※ 以下の前提です。

  • 一般ユーザがアクセスする方を App、弊社からのみアクセスする管理画面を Admin
  • App, Admin 共に同じ Rails プロジェクトがデプロイされている

Point

  1. 本番環境 App のみ ECS 起動タイプ Fargate

    • ややコストは上がるものの、スケーラビリティに柔軟性がある
  2. その他は ECS 起動タイプ EC2

    • docker exec でコンテナに入りデバッグ可能にする
    • 前回の記事 の様に qa/* ブランチ毎のタスクが複数起動している為、タスク数が増えてもコストに影響しない
    • 本番環境 Admin は、高トラフィックとなる様なことは弊社ではない為、スケーリングを考慮する必要性がない。
    • 本番でも docker exec しコンテナに入りデバッグしたいという要望があり、Admin を 起動タイプ EC2 にすることで担保

開発時に有用だったこと

デプロイ

以下ブランチにマージすることで自動的に試験→デプロイする様にしています。

  • master
  • develop
  • qa/*

f:id:kenzo0107:20190619232701p:plain

CircleCI で試験をパスすると、 aws codepipeline start-pipeline-execution を実行し 指定の CodePipeline を開始する様にしています。

CodePipeline

こちらが実質 ECS へのデプロイをしている箇所です。

f:id:kenzo0107:20190528171331p:plain

デプロイ関連の処理は Capistrano でラップしています。

検証環境自動構築

f:id:kenzo0107:20190619235429p:plain

前回記事 検証環境の自動構築 をご参照ください。

ブランチ qa/* push により以下処理が実行され、検証環境が構築されます。

Rails master.key は?

AWS パラメータストアに登録しており、Rails イメージビルド時に aws ssm get-parameters で取得しています。

その他、イメージタグ付け( :tag_image )、ECR へ登録処理( :push_image_to_ecr )も併記しておきます。

namespace :rails do
  task :build_image do
    run_locally do
      within fetch(:deploy_work_path) do
        execute 'aws', 'ssm', '--profile', fetch(:profile).to_s,
                'get-parameters',
                '--with-decryption',
                '--region', 'ap-northeast-1',
                '--name', "/#{fetch(:application)}/rails/master_key",
                '--query', '"Parameters[0].Value"',
                '--output', 'text', '>', 'config/master.key'
        execute 'docker', 'build', '--no-cache=true',
                '-t', "#{fetch(:ecr_host)}/#{fetch(:env)}-#{fetch(:application)}-rails:#{fetch(:rails_tag)}",
                '--build-arg', "RAILS_ENV=#{fetch(:rails_env)}",
                '-f', 'docker/deploy/rails/Dockerfile', '.'
      end
    end
  end

  task :tag_image do
    run_locally do
      within fetch(:deploy_work_path) do
        execute 'docker', 'tag',
                "#{fetch(:ecr_host)}/#{fetch(:env)}-#{fetch(:application)}-rails:#{fetch(:rails_tag)}",
                "#{fetch(:ecr_host)}/#{fetch(:env)}-#{fetch(:application)}-rails:latest"
      end
    end
  end

  task :push_image_to_ecr do
    run_locally do
      within fetch(:deploy_work_path) do
        push_image_to_ecr("#{fetch(:ecr_host)}/#{fetch(:env)}-#{fetch(:application)}-rails:#{fetch(:rails_tag)}")
        push_image_to_ecr("#{fetch(:ecr_host)}/#{fetch(:env)}-#{fetch(:application)}-rails:latest")
      end
    end
  end
end

def push_image_to_ecr(image)
  execute 'ecs-cli', 'push', "#{image}",
          '--aws-profile', fetch(:profile).to_s,
          '--region', fetch(:region).to_s
end
イメージビルド処理短縮

以前は Rails イメージビルド時に asset_sync を利用し、 assets を S3 に同期していましたが、この同期処理に非常に時間が掛かっていました。

ですが、弊社フロントエンドエンジニア 村上 ( @pipopotamasu )medpacker により、 Sprockets によるアセットのビルド処理をしないようにした為、デプロイ時間が大幅に短縮されました。*2

是非以下ご一読ください。 tech.medpeer.co.jp

Rails メトリクスを Datadog へ送信

デプロイ後に Rails の以下メトリクスを Datadog に送信する様にしました。*3

  • Rails Load Time
  • Rails CodeStats
  • Gem Dependency Count

post rails metrics to datadog · GitHub

f:id:kenzo0107:20190625144500p:plain

こちらは以前 2018年9月12日 に開催された 『MedBeer -Rails開発での技術的負債との付き合い方-』にて、クックパッド社の 小室 直さん (@hogelog) の発表を参考にさせていただきました。

ありがとうございます!

tech.medpeer.co.jp

技術的負債となる指標をプロジェクト初期から意識することで、返済への意識も育まれると思います。(願い)

ロギング

f:id:kenzo0107:20190619235524p:plain

Lambda で LogGroup を S3 に日時バックアップする処理はこちらの Serverless Framework プロジェクトで構築しています。

github.com

ログ閲覧

CloudWatch Logs Insight で非常にログの閲覧がスムーズになりました。

以下の様なクエリで、logStream を rails をプリフィックスとしフィルターをかけると、Rails コンテナのログを抽出できます。

fields @timestamp, @message
| sort @timestamp desc
| limit 20
| filter @logStream like /^rails/

f:id:kenzo0107:20190530222814p:plain

時系列で複数コンテナログを閲覧したい場合は、以下の様にすれば簡単に取得できます。

fields @timestamp, @message, @logStream
| sort @timestamp desc
| limit 20
| filter @logStream like /^rails|^nginx/

以上からインサイトで検索しやすい様、ECS の Service 単位でコンテナのロググループは統一しています。

まとめ

  • 負荷の多い箇所は Fargate がオススメ
    • その他は EC2 がコスト的に良い
  • デプロイ自動化
    • テストは CircleCI、デプロイは CodePipeline と役割分け大事
  • Rails config/master.key は AWS パラメータストアで管理
  • medpacker で脱 webpacker & デプロイ時間短縮
  • Rails メトリクスを Datadog に Post で定点観測
  • ログは CloudWatch Logs に一時保存
    • 日次で S3 保存し長期保存
    • CloudWatch Logs Insight でログ閲覧がスムーズ
    • ECS Service 毎にロググループを統一しとくとコンテナ毎の時系列ログが確認しやすい

上記に加えて、開発時に最も有用な、ECS を利用した検証環境自動構築については、またの執筆の機会にと思います。

以上、参考になれば幸いです。


(☝︎ ՞ਊ ՞)☝︎是非読者になってください


メドピアでは一緒に働く仲間を募集しています。 ご応募をお待ちしております!

■募集ポジションはこちら

https://medpeer.co.jp/recruit/entry/

■開発環境はこちら

https://medpeer.co.jp/recruit/workplace/development.html

*1:ssh コンテナを起動させ、アクセスさせることは可能です。

*2:大凡15→8分程度に短縮

*3:Gem の最新度については、現在、定期的な bundle update 当番により解消している為、送信していないです。

メドピアエンジニアに聞いてみたアンケート

こんにちは。 メドピアでエンジニアのゆるふわマネジメントをしている@arihhです。

事業拡大とともにエンジニアの数が増えている弊社ですが、 今回はそんな弊社エンジニアにどんな人がいるのか社内外向けに知ってもらうべく、 エンジニアのみなさんにアンケートをとってみました。

Q1.エディタは何を使っていますか?

みんな大好きエディタ論争です。 f:id:arihh:20190516141551p:plain

社内のemacs・vim戦争の現状をお見せしようと思ってのアンケートだったのですが、 VSCodeが2/3を占めるという一強状態の結果となりました。

Q2.shellは何を使っていますか?

エディタは何を使っているのかのアンケートは見たことがあるのですが、 開発のお供のshellはあまりみたことがないので聞いてみました。

f:id:arihh:20190516152555p:plain

こちらはfish・zsh・bashの3すくみとなりました。

Q3.はじめてHello Worldした言語は?

普段業務ではRubyを書いているエンジニアが多い弊社ですが、初めてのプログラミングを学んだときの言語を聞いてみました。

f:id:arihh:20190516184926p:plain

PHP・Rubyという人は0でした!!

初めての言語はRuby以外の言語の人が多く、またBASICからプログラムをやっている人も多いようです。 その他ですが、VB、MATLAB、Delphiなどの声がありました。

Q4.マウスやキーボードは何を使っていますか?

メドピアでは入社したエンジニアにMacBookPro(JIS/US選択可)を支給していますが、 それ以外にもキーボードを持参してきている方も多いので聞いてみました。

こちらはキーボードは標準のままが半数、HHKBが2割、Mac純正品が1割強といった結果でした。 また、キーボード・マウスの組み合わせでは以下のような回答がありました。

  • Majestouch MINILA / Logicool MX ERGO
  • ARCHISS ProgresTouch TKL US配列(Cherry MX 静音赤軸) / Kensington ExpertMouse ワイヤレストラックボール K72359JP
  • HHKB Pro type-S / SlimBlade Trackball
  • MiSTEL BAROCCO MD600(分離キーボード) / Apple Magic Trackpad 2
  • 素手

Q5.メドピアのいいところは?

ちょっと弊社のアピール的な内容になりますが、メドピアのいいところを聞いてみました。 挙がった声をまとめるとこんな感じです。

  • 働き方
    • 裁量労働制のため勤務時間が自由
    • 出社時間が自由
  • 制度や環境
    • テックサポート制度
    • 各種補助制度
    • エンジニアの成長補助が手厚く、Rubyコミュニティへの貢献もしている。社内勉強会も活発。
    • オフィスが静かで良い、集中出来る。色々な場所で仕事もOK
  • 風土・文化
    • 個々に裁量があり、決定する際にも議論がある所
    • 手を挙げれば挑戦させてくれるところ
    • 自分から手を伸ばせばなんでもできる、やらせてもらえるところ
    • コードをリファクタリングしていく文化
    • エンジニア文化が根付いている!医療に関わる事業なので社会貢献に直結している!
    • スキルアップ・新技術選定を柔軟に検討できるところ!
  • メンバー
    • 自分にできないことをできる人が近くにいて、安心して挑戦できるところ
    • 落ち着いたエンジニアが多いと感じるところ
  • CTO
    • CTOの雰囲気
    • なんか文句あってもCTOにいえば解決してくれる
    • CTOにメンションすると本を買ってもらえるところ

裁量労働制が本当に裁量労働制として勤務時間に裁量をもって業務ができたり、 テックサポート制度やRubyKaigiの参加などを会社が支援してくれたりするのはいいと思います! また、リファクタリングをしていく文化やCTOをイジる文化があるのもいいところかなと思います。

Q6.影響を受けた本は?

こちらの結果は思った以上にバラバラになってしまったので、並べて公開します (Amazonさんへのリンクはないです💦)

  • 達人プログラマー
  • エクストリームプログラミング
  • SCRUM BOOT CAMP THE BOOK
  • カイゼン・ジャーニー
  • ティール組織
  • [24時間365日]サーバ/インフラを支える技術 ……スケーラビリティ,ハイパフォーマンス,省力運用
  • カッコウはコンピュータに卵を産む
  • 実践ドメイン駆動設計
  • エンタープライズアプリケーションアーキテクチャパターン
  • オブジェクト指向のこころ
  • イノベーションのジレンマ
  • プロを目指す人のためのRuby入門
  • メタプログラミングRuby
  • 達人に学ぶ SQL徹底指南書
  • UNIXという考え方―その設計思想と哲学
  • 初めてのPerl
  • リーダブルコード
  • シリコンバレー流世界最先端の働き方
  • Exceptional C++
  • ブッダの教え一日一話
  • 自分の小さな「箱」から脱出する方法
  • すべてがFになる
  • ぼくらの七日間戦争

Q7.好きなコマンド

最後はネタ項目です!

peco
fzf
git checkout branchname origin/branchname
# shutdown -r now
git diff --cached
tail -f
vmstat(だが、medpeerのサーバに入ってない)
tap
hub
grep
z - https://github.com/rupa/z
whois
yes "sl" | sh
history | grep "hoge"
man
grap..less..?
open

最後に

アンケートに協力していただいたエンジニアの皆さんに、この場を借りて協力ありがとうございました。

今回のアンケートで想像以上に個性豊かなメンバーが集まっていることが再確認できました。 今回の記事で雰囲気など掴んでいただければうれしいです。


(☝︎ ՞ਊ ՞)☝︎是非読者になってください


メドピアでは一緒に働く仲間を募集しています。 ご応募をお待ちしております!

■募集ポジションはこちら

https://medpeer.co.jp/recruit/entry/

■開発環境はこちら

https://medpeer.co.jp/recruit/workplace/development.html

Ruby2.7の(実験的)新機能「パターンマッチ」で遊ぶ

はじめまして、メドピアのサーバサイドエンジニアの草分です。

RubyKaigi2019 1日目のセッションにてRubyのexperimental(実験的)な新機能「パターンマッチ」(Pattern Matching)が発表されましたね。

speakerdeck.com

この記事では発表で紹介されたパターンマッチの各種機能を確認し、遊んでみます。

概要

Rubyのパターンマッチとは?

Rubyist向けの説明として以下のような説明がなされていました。

  • データ構造の評価によるcase/when条件分岐の提供
  • 変数への多重代入

実際に例文を見てみましょう。

例文

case [0, [1, 2, 3]]
in [a, [b, *c]]
  p a #=> 0
  p b #=> 1
  p c #=> [2, 3]
end

case/whenではなくcase/inが追加され、caseで指定したオブジェクトとinに記載したデータ構造(パターン)を比較し、マッチした場合処理が行われます。

また、マッチした場合はパターンに記載した各変数に値がバインドされ、内部の処理で利用することが可能となります。


通常の多重代入とは異なり、オブジェクトのデータ構造とパターンが一致しない場合は処理されません。

case [0, [1, 2, 3]]
in [a]
  :unreachable # 配列構造が異なるため処理されない
in [0, [a, 2, b]]
  p a #=> 1
  p b #=> 3
end

その他、Hashもサポートされています。

case {a: 0, b: 1}
in {a: 0, x: 1}
  :unreachable # Hashの構造が異なるため処理されない
in {a: 0, b: var}
  p var #=> 1
end

それではこの機能が実装されているRubyを導入し、実際に試してみましょう。

準備

最新のRubyを導入する

  • まずは最新のRubyのソースコードをcloneし、trunkをチェックアウトします。
$ git clone https://github.com/ruby/ruby.git
$ cd ruby
$ git checkout origin/trunk
$ autoconf
$ ./configure
$ make
$ make install
  • しばらく待てば最新Rubyの完成です。

  • rbenvをお使いの場合、2019年5月時点では2.7.0-devバージョンを用いるとパターンマッチが利用可能なrubyをインストールすることができます。
$ rbenv install 2.7.0-dev

解説

基本文法/仕様

試す前に、パターンマッチの基本文法を確認してみましょう。

case expr
in pattern [if|unless condition]
 ...
in pattern [if|unless condition]
 ...
else
 ...
end

case 句に記載したものを検査対象として、以下のように動作します。

  • 記載したパターンを上から順番に評価し、最初にマッチしたものが処理される。
  • 1つもマッチしない場合はelse句の内容が処理される。
  • 1つもマッチせず、else句も存在しなかった場合は NoMatchingPatternError例外が発生する。

また、if/unlessによるguardも可能となっています。

case [1, 1]
in [a, b] unless a == b
  :unreachable # ここは処理されない
end

パターンマッチの基本文法は以上ですが、実行するとwarning: Pattern matching is experimental, and the behavior may change in future versions of Ruby!という警告が表示されます。
警告の通り、パターンマッチは実験的な機能であり将来振る舞いが変更される可能性があります。ご注意ください。

次は、現時点で利用可能なパターンの記法を1つずつ確認してみましょう。

Value pattern

case 0
in 0
in -1..1
in Integer
end
  • ===で比較して一致した対象にマッチします。
  • そのため、上記3パターンはいずれもマッチさせることができます。
    • ※処理が実行されるのは最初にマッチしたパターンのみです。

Variable pattern

case 0
in a
  puts a #=> 0
end
  • 任意の値にマッチし、マッチした値はローカル変数としてバインドされます。
case [0, 1]
in [_, _]
  :reachable
end
  • また、変数として利用しない場合は_を使うことができます。

Alternative pattern

case 0
in 0 | 1 | 2
  :reachable
end
  • | を用いることで複数のパターンにマッチさせることができます。

As pattern

case 0
in Integer => a
  a #=> 0
end
  • =>を用いることでマッチした値を任意の変数にバインドすることができます。
  • rescueの使い方と似た記法ですね。
    • rescue StandardError => e

Array pattern

ここから少し複雑になってきます。

Array patternでは以下のいずれかの記法がパターンとして利用できます。

pat: Constant(pat, ..., *var, pat, ...)
   | Constant[pat, ..., *var, pat, ...]
   | [pat, ..., *var, pat, ...] # BasicObject(...)のシンタックスシュガー

そして以下の条件を満たすとマッチします。

  • Constant(何らかのclassを指定) === 検査対象objectがtrueを返す
  • 検査対象objectの#deconstruct メソッドが配列を返す
  • そこで返した配列と指定したパターンがマッチする

この#deconstructメソッドの使い方が鍵になります。
まずはArrayを対象としたArray patternのパターンマッチから見ていきましょう。

class Array
  def deconstruct
    self
  end
end

case [0, 1, 2] # ↓4種のいずれの記法でもマッチ可能
in Array(0, *a, 2)
in Object[0, *a, 2]
in [0, *a, 2] 
in 0, *a, 2   # []は省略可能
end

次はStructを対象としたArray patternのパターンマッチを見てみましょう。

class Struct
  alias deconstruct to_a
end

Color = Struct.new(:r, :g, :b)
color = Color[255, 0, 0]

case color
in [0, 0, 0]
  puts "Black"
in [255, 0, 0]
  puts "Red"
in [r, g ,b]
  puts "#{r}, #{g}, #{b}"
end

このように、独自に#deconstructを定義することで任意のobjectに対してArray patternによるパターンマッチを行うことができるようになります。
これがRubyのパターンマッチの大きな特徴の1つとなっています。

Hash pattern

Hash patternでは以下のいずれかの記法がパターンとして利用できます。

pat: Constant(id: pat, id:, ..., **var)
   | Constant[id: pat, id:, ..., **var]
   | {id:, id: pat, **var} # BasicObject(...)のシンタックスシュガー

そして以下の条件を満たすとマッチします。

  • Constant(何らかのclassを指定) === 検査対象objectがtrueを返す
  • 検査対象objectの#deconstruct_keys メソッドがHashを返す
  • そこで返したHashと指定したパターンがマッチする

前述のArray patternと似た形式で、Hashのようにkey名を指定してパターンマッチを行うことができます。

class Hash
  def deconstruct_keys(keys)
    self
  end
end

case {a: 0, b: 1} # ↓4種のいずれの記法でもマッチ可能
in Hash(a: a, b: 1)
in Object[a: a]
in {a: a}
in {a: a, **rest}
  p rest #=> {b: 1}
end

こちらも独自に#deconstruct_keysを定義することで任意のobjectに対してパターンマッチを行うことができるようになるため、Rubyのパターンマッチの大きな特徴の1つとなっています。

また、#deconstruct_keysの引数keysには処理の効率化のためのヒントとして、パターンの処理に必要なkey名の配列が渡されます。 その配列に含まれないkeyについては返却せず無視してよく、処理コストの削減を行うことができます。

遊ぶ

クイックソートしてみる

「クイックソートはパターンマッチを用いると書きやすい」という情報を社内で耳にしたため試してみます。

処理対象は数値の配列であることを前提として、ソート処理を書いてみました。

def qsort(ary) 
  case ary
  in [piv, *xs] # 要素が2個以上
    lt, rt = xs.partition{|x| x < piv}
    qsort(lt) + [piv] + qsort(rt)
  else # 要素が0個または1個
    ary
  end
end

ピボットの位置は先頭で決め打ちですが、かなりシンプルに書けますね!

逆にパターンマッチを使用せずに書くとすればこんな感じでしょうか。

def qsort2(ary)
  return ary if ary.length <= 1
  piv = ary[0]

  lt, rt = ary[1..].partition{|x| x < piv}
  qsort2(lt) + [piv] + qsort2(rt)
end

比べてみると、配列長の確認や配列の分割が冗長に見えてきますね。 オブジェクト構造による条件分岐と変数へのバインドが同時に行えるパターンマッチは、かなり強力な文法なのではないでしょうか。

独自拡張クラスを作ってみる

Hash patternをHash以外で使ってみたかったので、Timeクラスを独自拡張し、令和表記で年号を返すメソッドを作ってみました。
As patternと組み合わせて利用しています。

class Time
  def deconstruct_keys(keys)
    { year: year, month: month, day: day }
  end

  def to_reiwa
    case self
    in year: 2019, month: (5..)
      "令和元年"
    in year: (2020..) => year
      "令和#{year - 2018}"
    else
      "not令和"
    end
  end
end

実行してみます。ちゃんと動きましたね。

p Time.local(2019, 5, 1).to_reiwa  # => 令和元年
p Time.local(2019, 8, 1).to_reiwa  # => 令和元年
p Time.local(2020, 1, 1).to_reiwa  # => 令和2年
p Time.local(2019, 4, 30).to_reiwa # => not令和

引数の型指定をしてみる

当日の発表では「引数に対してパターンマッチを実行可能とするのは技術的には可能だが、型の指定に使われることが想定されるので実装しなかった。」という内容の発言がありました。

ということで言いつけを破ってTryしてみましょう。

def arg(num, str)
  case [num, str]
  in [Integer, String]
    :ok
  end
end

このメソッドを呼び出してみると…

p arg(3, "3")     # => :ok
p arg("2", 2)     # => NoMatchingPatternError
p arg(:a, "a")    # => NoMatchingPatternError
p arg(1, :a.to_s) # => :ok

引数の型を間違えると例外をraiseするメソッドになりました! 全くRubyらしくありませんね。

まとめ

RubyKaigi2019で紹介されたパターンマッチの機能を一通り触ってみました。まだ実験的な機能であるとのことで仕様変更の可能性はありますが、正式リリースが楽しみな機能です。

この他にもRuby3で実装が予定されている静的解析や、逆に搭載が見送られた仕様など、将来のRubyに関する具体的な情報が次々発表されたRubyKaigi2019でした。今後のRubyの動向から目が離せませんね!


(☝︎ ՞ਊ ՞)☝︎是非読者になってください


メドピアでは一緒に働く仲間を募集しています。 ご応募をお待ちしております!

■募集ポジションはこちら

https://medpeer.co.jp/recruit/entry/

■開発環境はこちら

https://medpeer.co.jp/recruit/workplace/development.html