圧倒亭グランパのブログ

30年後の自分にもわかるように書くブログ

crystal.tokyo #7 で Concurrency について話しました

2018/07/18 に crystal.tokyo #7 を開催しました。

crystal.connpass.com

前回の「Crystal勉強会」から名称を変えて「crystal.tokyo」としました。 crystal-jp.slack.com でご意見を募ったところ、「crystal.tokyo」が一番票を得たので変更しました。「登壇者&聴衆」という勉強会スタイルに縛られずに、もっといろんな形で交流できたら良いなと思っています。

毎回のごとく、開始前からお酒を開放。

ピザ寿司が届くと同時に、LTを開始しました。

Concurrency

最初は自分でした。Concurrencyについて話しました。

speakerdeck.com

図を用いて説明しようとしたのですが、図解がかなり難しかったので諦めました。発表後に「Goにあるような『メッセージのバッファ数』を指定することはできるか」という質問がありました。「CrystalのChannelでも指定できます」とお答えしましたが、改めてここでコードを紹介します。下記のConcurrencyに関する公式ドキュメントの「Buffered channels」で説明されています。

Concurrency · GitBook

本家のコードを引用します。

# A buffered channel of capacity 2
channel = Channel(Int32).new(2)

spawn do
  puts "Before send 1"
  channel.send(1)
  puts "Before send 2"
  channel.send(2)
  puts "Before send 3"
  channel.send(3)
  puts "After send"
end

3.times do |i|
  puts channel.receive
end

出力は以下です。

Before send 1
Before send 2
Before send 3
1
2
After send
3

Channel(Int32).new(2) の部分でinitializeの引数としてメッセージのバッファ数を指定しています。するとこのChannelは以下のような特徴を持ちます。

  • sendを呼び出したときは、メッセージがバッファ数に達するまで蓄積する
    • メッセージ数がバッファ数に達していない場合、sendを呼んでもFiberの切り替えは行われない
    • 蓄積されたメッセージはインスタンス変数 @queue に格納されている
  • receiveを呼び出したときは、蓄積されたメッセージを順に返す
    • 複数回receiveを呼び出したら、 @queue から順にshiftされてメッセージが返ってくる
    • メッセージが蓄積されていない場合はFiberの切り替えが行われる

この挙動に注意して処理を追うと、上記の出力結果になることがわかると思います。

Crystal in Operation

次は @arcage さんです。地方からのご参加でした。タイトルは 「Crystal in Operation」。

speakerdeck.com

日々のネットワーク周りの業務で使うツールをCrystalで書かれているお話でした。システム周りの情報を取得するClassの紹介だったり、ファイル操作に便利なClass、プロセス周りの情報を取得するClassなど、自分は知らなかった有用な情報が盛りだくさんでした。逆にCrystalに足りない部分も言及されています。結論はぜひ資料を御覧ください。

Crystalのインスタンス変数の型推論について

最後は @make_now_just さんです。インスタンス変数の型推論のお話でした。

slides.com

rubyの場合はnilが返るがcrystalの場合はコンパイルされない、という話や、「Crystalのインスタンス変数の型推論の内容はエラー文に書いてある」ということなど、型推論に関することを発表されていました。発表のあとは、 @make_now_just さんのPRの話に移ったり、ライブコーディングで「このケースではどういった型推論になるのか」というのを会場全員で考えて確認する、という流れが生まれました。こういうの、良いイベントだなぁと思った瞬間です。

話題になったのが、次の例では @arg の型はなんでしょう?というものです。

class Hoge
  getter arg

  def initialize(@arg : Int32)
  end

  def initialize(@arg : String)
  end
end

hoge = Hoge.new("string")

puts typeof(hoge.arg) # => ここは何が表示される?

正解は (Int32 | String) です。ここは会場でもある程度予想はできていました。では、次はどうでしょうか。

class Hoge
  getter arg : Int32 # ここで型を定義する

  def initialize(@arg) # ここで型は定義しない
  end

  def initialize(@arg : String)
  end
end

hoge = Hoge.new("string")

puts typeof(hoge.arg) # => ここは何が表示される?

正解はコンパイルエラーです。

$ crystal hoge.cr
Error in hoge.cr:11: instantiating 'Hoge:Class#new(String)'

hoge = Hoge.new("string")
            ^~~

in hoge.cr:7: instance variable '@arg' of Hoge must be Int32, not String

  def initialize(@arg : String)
                 ^~~~

このコンパイルエラーは Hoge.new("string") の部分で起きています。しかしこの場合、 def initialize(@arg : String) のメソッド定義の部分でエラーになっても良いのでは?という意見が出ていました。確かになるほどと考えさせられる内容でした。

こういった形で、会場が一体となって意見交換できるのはとても良いなと思いました。

そのまま懇親会へ

個々の会話ベースの懇親会もいろいろ話すことができて良いのですが、今回は「Crystalの話題にフォーカスし、個々人の困っている点や疑問を会場で共有しよう」ということにチャレンジしました。形式としては、

  • わいわい周りの人と話しても良い
  • ピザ寿司食べていても良い
  • なにか疑問に思ったことがあったら、プチLTのように前に出て、会場に疑問を投げかける
  • 会場の人は興味があれば一緒に考えて意見を交換する

という形です。何かを強制することはありません。登壇者の話を必ず聞く必要もないですし、必ず誰かが登壇しなければいけないということもありません。疑問や困りごとがあったらみんなに聞こう!というスタンスです。

どうなるかわからなかったのですが、結果的に会場から以下の話が出てきました。

  • デバッグってどうしていますか
  • WAFを使っているCrystalコードのレビュー
  • Webサーバーのテストってどうしていますか
  • 別の勉強会で話したLTをここでもします!

それぞれいい感じに意見交換できてよかったです。また、ネタが尽きたとしても自然とわいわいと会話発生し、良い雰囲気だなと思いました。

まとめ

まだまだ発展途上で至らない点も多かったので、今後もどんどん改善していきたいです。

Crystalの普及ももちろんですが、「ハードルは低く、満足度は高く」を目指して、イベント開催のPDCAを回していけたらと思います。