Skip to content
This repository has been archived by the owner on Apr 13, 2020. It is now read-only.

pacuum原稿 #7

Merged
merged 3 commits into from
Oct 23, 2017
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
校正
pacuum committed Oct 1, 2017
commit 77f14506fcdda21f5ccaf42a9e8b942ad2a41068
11 changes: 7 additions & 4 deletions pacuum/README.md
Original file line number Diff line number Diff line change
@@ -5,10 +5,12 @@ Twitter: @pacuum
# はじめに
はじめまして。@pacuumと申します。普段はベトナムのハノイでベトナム国内向けプロダクトの開発をしています。最近はあまりコードは書かなくなったのですが、自分のプロダクトマネージャーとしての職権を乱用して crystal をリコメンドエンジンの実装などに実践投入したりしています。とはいうもののまだCrystalの経験は多くありません。

私が Crystal を使うようになったのはRuby的シンタックス、高速、静的型などの理由もあるのですが、一番の理由はCrystal の型チェックやマクロが他の言語ではあまり見られないユニークな特徴を持っていると感じたからです。この記事ではそのような特徴を使ってできそうなこと、を幾つか小ネタとしてお話しようと思います
私が Crystal を使うようになったのはRuby的シンタックス、高速、静的型などの理由もあるのですが、一番の理由はCrystal の型チェックやマクロが他の言語ではあまり見られないユニークな特徴を持っていると感じたからです。この記事ではそのような特徴を使ってできそうなことを幾つか小ネタとしてお話しようと思います

# 1. クラスのサブセット
私は長い間 Rails で開発をしてきました。Rails はとても素晴らしいフレームワークなのですが、長期間使い続けると色々な問題に悩まされるようになりました。最も顕著な問題はパフォーマンス、特に ActiveRecord のパフィーマンスです。複雑な処理をしようとして多数のレコードや関連データを取得するといとも簡単に大量のメモリを消費して処理が非常に遅くなってしまいます。これを回避するために「不要なフィールドを取得しないことで高速化する」とテクニックがしばしば用いられます。このような「あるモデルのフィールドのサブセットからなるモデル」を作りたいという状況を考えます。これは ActiveRecord にかぎらず起こりうる状況かと思います。
私は長い間 Rails で開発をしてきました。Rails はとても素晴らしいフレームワークなのですが、長期間使い続けると色々な問題に悩まされるようになりました。最も顕著な問題はパフォーマンス、特に ActiveRecord のパフィーマンスです。複雑な処理をしようとして多数のレコードや関連データを取得するといとも簡単に大量のメモリを消費して処理が非常に遅くなってしまいます。これを回避するために「不要なフィールドを取得しないことで高速化する」とテクニックがしばしば用いられます。

このような「あるモデルのフィールドのサブセットからなるモデル」を作りたいという状況を考えます。これは ActiveRecord に限らず大量のデータを扱う際には一般的に起こりうる要求かと思います。

例として以下のようなモデルを考えます。`User` は `email`, `user_type_id`, `created_at` の3つのフィールドを持つとします。

@@ -35,7 +37,7 @@ end
subset_users = User.select( :email, :created_at ).all
```

これは実際にはクラスとしては `User` のままでただ単に保持しているフィールドの種類が少ないというだけのものです。したがって `User` クラスに定義されているメソッドは全て呼び出すことができてしまいます。`gmail?` と `recent_user?` については中で使用されているフィールドが `select()` に含まれているので正しい結果を返します。一方で `free_user?` を呼び出した場合 `user_type_id` が定義されていないため実行時例外が出ていまいます。このように、どのメソッドなら呼び出しても安全なのかがすぐにわからないのが `select` の辛いところであり、これを多用するとバグの袋小路に嵌っていくことは明らかです。フィールドのサブセットごとに必要に応じてサブクラスを定義することも考えましたが、フィールドのサブセットによって実行可能なメソッドのサブセットも変わり、かつその判断が非常に難しいため、メンテナンスのことを考えるととても管理できないと思い断念しました。
これは実際にはクラスとしては `User` のままでただ単に保持しているフィールドの種類が少ないというだけのものです。したがって `User` クラスに定義されているメソッドは全て呼び出すことができてしまいます。`gmail?` と `recent_user?` については中で使用されているフィールドが `select()` に含まれているので正しい結果を返します。一方で `free_user?` を呼び出した場合 `user_type_id` が定義されていないため実行時例外が出てしまいます。このように、フィールドのサブセットごとに呼び出し可能なメソッドのサブセットが決まるという状況が発生するのですが、どのメソッドなら呼び出しても安全なのかがすぐにわからないのが非常に厄介な問題であり、これを多用するとバグの袋小路に嵌っていくことは明らかです。フィールドのサブセットごとに必要に応じてサブクラスを定義することも考えましたが、フィールドのサブセットによって実行可能なメソッドのサブセットの判定は人間がやるには難しすぎるため、メンテナンスのことを考えるととても管理できないと思い断念しました。

類似の状況として、`User` データをCSVファイルなどから一括登録したいがその時点では `user_type_id` が決まっていないというような、運用フェーズのメンテナンスで起こり得る状況について考えます。このような場合にとりあえず `user_type_id` に `nil` を代入しておいて処理を進めることはできますが、その処理の過程で誤って `free_user?` が呼ばれてしまった場合に嘘の結果を返してしまって危険なので避けたいところです。

@@ -70,7 +72,8 @@ Test.new.a
私は技術のパラダイムの変遷においては常にバブルが起きるというか、一旦行き過ぎて揺り戻しが起こりながら最適なところを目指していくように感じています。20年ほど前は C や Java のような静的で堅い言語が主流でしたがその後 Ruby や Python のような動的で柔らかい言語が硬い言語への反発として使われるようになりました。その後動的言語の辛さが広く認識されるようになり、型推論やNull安全性などを活用した、静的だけど硬すぎない言語が次々と現れて静的言語への揺り戻しが起こっています。ですが個人的にはダックタイピングに慣れた身にはこの新しい言語はやや硬すぎると感じていました。そこで出てきたのがCrystalです。正直初めて Crystal のことを知ったときは「Ruby のシンタックスを真似たイロモノ言語」という印象だったのですが仕様を調べるうちにそのような認識は全くの誤りで、これはさらにもう一度揺り戻しが起きた先の次次世代言語ではないか?と認識を改めるようになりました。私はあまり沢山の言語を知っているわけではないので見当違いのことを言っているかもしれません。閑話休題。

## Proof of Concept
さて、この Crystal の特徴があれば冒頭のようなサブセット問題を解決することができます。まずはモデルの全てのメソッドを独立した `module` として定義します。
さて、この Crystal の特徴があれば冒頭のようなサブセット問題を解決することができます。まずはモデルの全てのメソッドを独立した `module` として定義します。`User::Functions` モジュールにはメソッドのみが定義されており、どのようなフィールドを持つかという情報は含まれていません。


```ruby
class User