プログラミング漫遊記

思ったことや、勉強したことをつらつらと。

RubyKaigi 2023 に行った

長野県松本市 まつもと市民芸術館で5/11 ~ 5/13まで開催された RubyKaigi 2023に行ってきたので感想を書いておきたい。

僕自身はRubyKaigiの現地参加は去年に引き続き2回目になる。去年は1割程度しか理解できなかったので去年より理解できるといいなーと思いながら参加した。

なお、会期中の出張費(宿泊費や交通費)に関しては所属先の永和システムマネジメントにご負担していただいています。ありがとうございます。

事前

RubyKaigiのスポンサーだったので社の企画の会議に参加したり、Rubyメソッドかるたの作成などをしていた。他にはぼっち回避のためにKyoto.rbに参加したり、RubyKaigiで出てくるであろう単語をおさらいしたりした。(YJITとかASTとか)

あと直接的な関係はないけど、研鑽Rubyのβ finalを7章ほど読んでおいた。(本当は全部読んでおきたかった)

Day 0

午前中は仕事して午後から松本に向かった。新幹線で名古屋まで出て、特急しなので松本へ。 新幹線の中でAtCoderをしていたら気持ち悪く成ったので特急しなのでは大人しくしていた。

この日はKeebKaigiなるものをやっていてキーボード好きが松本に集っていた。僕はキーボードには興味がないため参加しなかったのだけど、後からいろんな人に面白かったよと聞いて、動画を見たらこれまた面白い。「トリのAaronさんの動画は絶対見るべき」という話で見始めたのだけど、気づいたら全編見返していた。

https://www.youtube.com/watch?v=Lk3-5ceJz4Y

自分が普段使うキーボードに興味があるかと言われればそんなになんだけど、こうやって何か面白そうなことをしている人には興味がある。真剣にふざけている人はかっこいい。(もちろんふざけてない真剣も然り)

来年も開催されるなら行ってみたいなぁと思った。

Day1

これ以降は特に印象に残ったトークだけ感想を書いていくことにします。去年はトークは全部見た(もちろん裏番組は無理だけど)のだけど、今年は1,2個逃しちゃった。

The future vision of Ruby Parser

この日一番印象に残っていたトーク。金子さんのパーサーの話。 いや、正直難しくてところどころ理解できなかったけど、なんだか聞いた後会場がすごい熱気を帯びていたことを覚えている。

自分はメモをとりながら聞いていて、わからないところはキーワードだけでもメモっておいた。

パーサーとは何かから始まって、Rubyで解かなければいけない問題3つ(Usaiblity, Maintainerbility, Universal Parser)と初心者にもわかりやすいように全体地図が見えるように発表されていたのが印象的だった。

Rubyには4つのdoがあって、、、みたいな話をmatzがしていて柔軟性の点を評価していたのに対して、金子さんは「これはひどい」という話をしていたのが個人的には面白かった。視点が変わればなんとやらw

パーサーに少し興味が出てきたのでここらへんを読んで勉強していきたいと思った。 https://i.loveruby.net/ja/rhg/book/yacc.html

Bison id dead.

Make Regexp#match much faster

正規表現でReDos対策をした話。比較的馴染みがあって(ほんとか?)わかりやすかった。

Ruby正規表現はパワフル。それゆえの問題がマッチング時間の指数関数的爆発問題。 その爆発的な時間を利用した攻撃ReDosを対策した時の話をされていた。

結果、線形時間でマッチングできるように成っていてグラフが直線になっていてすごかった。

正規表現エンジンをRubyでかければいいなぁ」という今後の展望を話されていたけど、これがどういう意味なのかはよくわかってない。わかってないけど、ちょっとワクワクした。

線形時間かどうかを判定するlinear_time?メソッドも便利そうだから覚えておきたい。 https://bugs.ruby-lang.org/issues/19194

UTF-8 is coming to mruby/c

今泉さんのStirngのお話。舞台はMRI(CRuby)ではなく、mruby/c。

この発表を聞くまでmruby/c と mrubyが違うことすら知らなかったぜ😅

文字コードに知見がなく今までなんとなく使ってきた文字列だけどUTF-8を実装していないだけでちゃんと文字が数えられなかったりインデックスが取れなかったりすることがわかった。

内容はソースコードがあるのでとてもイメージしやくString初心者でも理解しやすかった。 いつも思ってるけど、今泉さんはStringの話になるととても楽しそうに話すのが印象的(String以外が楽しくなさそうという話ではない)でこの楽しそうな雰囲気が人を惹きつけるのだろうなと思った。C会場での発表は立ち見がが現れるほどの大盛況だった。

String#trが多機能という話は深く頷いた。AtCoderでちょっとトリッキーな使い方するときにtrメソッドを使うけどいつも忘れてるりまみている気がする。 普段は使わないかな。変換したいだけだとgsubとかに落ち着く。

Power up your REPL life with types

katakata_irbの話。

メソッドチェーンした時はif分での分岐にもコード補完が正しく追随してくれるという話。凄すぎる。なんか、全体的にさらっとお話されていたけど、ものすごく大変なんじゃないかなと思ったりした。

デモに見とれてメモ取るの忘れてたので詳細な内容を全然覚えていないのが悔やまれる。どうやって実現してるんだろ。あとで見返したい。

普段からREPLにはお世話になっていて、irbはnodeとかdenoとかのREPLと比べてもとっても使いやすいと思っている。これがirbで使えるようになったら嬉しいなぁ。

LT

弊社から送った銅鑼が大活躍していた。 どの発表も時間の短さから勢いがあってRuby 30周年のLTとかの雰囲気を思い出した。

BINGOの話が印象に残っている。

Day2

Implementing "++" operator, stepping into parse.y

塩井さんの発表。

Rubyにインクリメント演算子++を実装する話。話というか物語で細かい部分(パーサーの話)がわからなくても楽しめる内容でした。

succメソッド使ったり+=と使ったりと色々試行錯誤してくのが面白かった。

Ruby++はもはやRubyではないので新しい名前が必要 → Ruby++

というオチが最高でいずれRuby#とかくるのかな。

この発表も普段から楽しそうに実装してるんだろうなというのが透けて見える良い発表でした。自分も何か手を動かさないとなと刺激をいただいた。

The Resurrection of the Fast Parallel Test Runner

koicさんの並列テストの話。

冒頭からロックな感じでRubocopの人と思われるのもなんなので違う話題でプロポーザル出したとのことだった。守備範囲が広くてすごい。

テストの高速化。お金で殴れたらそれが一番いいけど、そうでもないのが現場なのだろう。とても心に染みる。並列化をうまくやるしかない。

並列って言葉だけだとわかりにくいけど、スライドに図がたくさんあって、ワーカーが余っちゃうところとか理解しやすかった。働いていないワーカーをいかに減らすか。最初の方で出てきたコマンドrpsec -pで遅いテスト出せるの知らなかったので使ってみたい。

難しいところも多々あったけど、何回か発表見返せば理解できそうな感じだった。

Day3

Ruby Committers and The World

このトークがちょっと悔しくて、別の意味で印象に残っている。

去年はnumbered parameterで一つ目のブロックパラメーターにitを使うのはどうか?みたいな提案で周囲を巻き込んで議論していたのがとても面白くて印象に残っている。今年はなんで悔しい思いをしたのかというと全編デフォルトが英語だったからだ。英語がわからなさすぎて面白いんだろうなーという雰囲気だけしか味わえなかった。それでもところどころの話は字幕をみながら頑張ったのでこと後急激に疲れが出たのを覚えている。

Load gem from browser

Ruby WASMでいろんなgem を loadしたいというお話。

Day3から疲れ果ててメモを取るのを諦めたので記憶が怪しい。 ただこの話は去年のアドベントカレンダーで読んだことがあって、その続編だとわかって感動した。

https://qiita.com/ledsun/items/3f8ba1ee2699d546c18c

細かい話は覚えてないのだけど、WASMちょっと夢が広がるなぁと思った。

全編を通して

トークが中心なのはスポンサー巡りとかがあまりできなかったからだ。それでもクックパッドさんの冷蔵庫とかHelpfeelさんのハックスペースは印象に残っている。ハックスペースは居心地良さそうで、いつかここでモブプロ会とかしたいなぁ。

自分は1つのことにしか集中できない癖があって、トークもそれ以外も楽しんでいる人が羨ましかったりする。Twitterに投稿するのも結構集中がギリギリだったりする。来年はここらへあたり柔軟に楽しめるようになるといいなぁ。(でもトークが一番聞きたいのでここの集中力は落としたくない)

普段Rubyistとの交流がない自分にしては、会期中にたくさんのRubyistにも出会った(当社比)。フィヨルドブートキャンプ生で卒業してからアイコンだけ知ってる人などいろんな人が声をかけてくださってありがたい。他にも、たまに顔を出す程度の付き合いの人とも挨拶したり談笑したりできてよかった。来年を楽しむために今年はいろんなコミュニティに顔を出せたらと思っている。

Kaigi Effect

英語頑張りたい欲が最高潮に達した。会社で英語勉強したいと宣言していたらkasumi8ponが「TOEICの申し込み明日までですよ」と教えてくれた。ので、「えいや!」で申し込んだ。その時の様子。

学生時代は5教科で一番苦手だったのが英語で、こやつのせいで行きたい大学も色々変更せざるを得なかったわけだけど、その苦手意識と訣別する日が来たようだ。時間がかかるので焦らず取り組んでいく。

他にもKaigi Effectはある。Rubyメソッドかるたのメソッドの説明の校正中に Math.#sqrtが引数0を取った時の説明が不足していることに気づいたのでるりまに初PRを送って無事マージされた。

github.com

RubyKaigi 2023も自分の中では全力で楽しんだ。来年はもっと楽しめると思う。

オーガナイザー、スタッフ、スピーカー、スポンサー含めRubyKaigiに関わったすべてのRubyistに感謝します。

4月のふりかえり。コーヒーは程々に編。

学習と生活の記録。簡単に言えば日記みたいなもの。

先月までは半プライベートな別の媒体で振り返りを書いていたけど別に隠すものでもないので、はてなブログに書いていくことにした。逆に技術的なことは、ZennとかQiitaとかに書いて言ったほうがいいのかもなとか思ってる。

おしごと

今月はRubyメソッドかるたの告知ができたのがでかい。 2月くらいから?(あれ1月だったかな?)コツコツやってきたものが形になって世の中に告知できるというのは喜びもひとしおだ。細かい話は会社の開発者ブログで書くかもなので、ここではこれくらいにしておく。

blog.agile.esm.co.jp

技術的なことでは、初めてOpenAPIで仕様書の作成を行った。

https://swagger.io/specification/

JSONもしくはYAML形式でインターフェースを定義することで、ツールを使えばデータの不整合などに気づけるようになるみたい。

そのほかは、仕事の仕方を変えてみた。というと、なんか大袈裟な感じがするが仕事中に飲むコーヒーの量を変えてみた。社内で行われる月1回の集まりの時に「コーヒー飲み過ぎ出るんですよねー」という話をしたら他の人は午前と午後1杯ずつ飲んで、それ以外はお茶か水で済ましている人が多かった。なので同じように午前と午後に1杯ずつ飲むようにしたらお腹が痛くなることが減った気がする。気がするだけかもなのでしばらく続けていく。

日常

  • 大徳寺で精進料理食べた(京都)
  • Kyoto.rbに行ってきた(2回目)
  • 技術書以外の本を読むようにした
  • 週1でコメダ珈琲に通い出した
  • ジムはあんまり通ってない
  • PADAoneさんと話した
  • レタスの賞味期限が2回ほど切れた
  • 晩御飯の何作るか困ったら大体親子丼作ってた

Kyoto.rb

kyoto.rbはRubyKaigi2023でボッチにならないという目的のために行った。 仕事終わりに京阪電車乗っていったのだけど、見事に反対方面の電車に乗ってしまって、生きる力が日々なくなっていっている。つらい。 本編はLT会という名目だったがゆるーい感じでRubyKaigi情報を知れて楽しかった。

春眠暁を覚えず

あまりにも朝が気持ち良すぎて起きれない問題が自分の中で勃発している。春とは言わず年中だけど。

9時くらいに起きて9時半に仕事をするというのが板についてきたが、その場合ゴミ出しが間に合わない。そして、できれば朝はもう少し早く起きて学習に使いたい。

そんなこんなで朝早起き大作戦を決行した。自分の性格からして毎日気合いで起きるというのは土台無理な話。なので週1回だけ早起きする。普通に早起きするのは無理。なのでご褒美を設定する。

ということで毎週1回早起きしてコメダ珈琲にいって技術書以外の読書をすることにした。勉強したいんやなかったのかい。

これが功を奏し、今月は『ゴリラ裁判の日』と『限りある時間の使い方』という本を読むことができた。 しばらく続けていく。

技術的な会話

Zennの記事イベントループとプロミスチェーンで学ぶJavaScriptの非同期処理typoのpatchをひたすら送っていた縁でPADAoneさんとお話しする機会があった。どんなことに興味があって、どんなことをしていきたいのかなど技術的な話をできて楽しかった。

フィヨルドブートキャンプ以外の人との繋がりって社内以外だと少ないのでありがたい。

学習

アルゴリズムとデータ構造

先月に引き続き『アルゴリズムとデータ構造』を読んでいた。通称「けんちょん本」 なんとなく曖昧だったUnion-Findを書けるようにしたり、挿入ソートやマージソートクイックソートなどをRubyで書き直して理解を進めた。

ある程度読み終わったのでC問題埋めを引き続き頑張っていこうと思う。

研鑽Rubyプログラミング

β版のfinalが出た時点で読み直している。 なんとなくRubyKaigi 2023までに読み終われたいいなぁなんて思ってるが果たして。

前回読んだ時にはメタプロっぽい書き方がたくさん出てきて、一回で理解できないなと思っていたが、今回の読み直しでは今のところすっと理解できるようになってきた。

自分でライブラリを作って保守するなどの経験がないので、こういう書き方もあるのかぁというなんとなくぼんやりと捉えることにしておく。

プログラミングの基礎

前に7割くらい読んでたので今回は読了目的で読み直し。OCamlを使ってダイクストラの最短経路問題を解きながらプログラミングの基礎を習得するというコンセプトの本。会社の先輩にお薦めしてもらって読み始めた。2回目なのでいい感じの理解度。

AWS Amazon Web Servicesのしくみと技術がこれ1冊でしっかりわかる教科書

AWSなんにもわからないので、0 を 1くらいにはしておこうと読み始めた。7割くらい読んだけどまぁなんとなく概要はわかった。気がする。ような。

AtCoder

けんちょん本をやっていた関係でC問題は解けていないが、モチベーションが上がってきたので茶色diffのC問題埋めを頑張る。5月からはコンテストにも参加する。

5月に向けて

5月はTypeScriptかLinuxについてやっていきたい気持ち。 あとはRubyKaigi楽しむ。

【AtCoder】入茶のちゃちゃちゃ

AtCoder Beginner Contest 290でめでたく入茶したので色変記事というものを書いてみます。

最初に断っておくと、ものすごい人のものすごい入茶記事ではなく、平凡な感じの記事ですのであしからず!!

自己紹介

  • haruguchi
  • 社会人
  • 使用言語 Ruby
  • 2022年の8月頃から本格的にコンテストに参加し始める

雑感

ABCのA問題とB問題を取りこぼしなく速く解けるようになったら茶色になったという感じで、特別難しいアルゴリズムは覚えてないです。 ただ、最近やたらグラフの問題は出るのでDFS(深さ優先探索), BFS(幅優先探索), UnionFindあたりは勉強しました。(忘れました)

RubyでA,B問題解く場合はEnumerableモジュールと仲良くなるのが一番大切で、やってくうちにいろんな便利メソッドと友達になれたのがよかったです。(tallyメソッド最高)

「数学できないとダメでしょうか?」ということをよく聞かれるのですが、できた方が得だなぁという印象です。ただし、AやB問題で求められる数学の知識はそんなになくて、中学数学全般と高校数学の数A分野あたりが役に立ちました。計算量が厳しい問題は少ないので、別の方法で愚直にコードを書けば問題ないのかなと思っています。

そのほかは、解説を数式で理解できるのが一番の強みだと思いました。たまに数式見ても理解できない時は解説放送見てますが、、

やったこと

やったことは単純でABCの過去問をやる。これだけです。

  1. 楽しんでやる
  2. プログラミングコンテストAtCoder入門
  3. ABCのB埋め

楽しんでやる

メンタル的な部分。楽しんでやれるように参加の仕方を考えました。

もともと、通っていたプログラミングスクール内(フィヨルドブートキャンプ)でモブプロをやりたくて、その題材に丁度良かったのがAtCoder ABCでした。 週1回モブプロ会を開催してA, B問題を解いてるうちに楽しいなーと思ったのが源流なので、この気持ちを大事にしながら参加しています。

haruguchi-yuma.hatenablog.com

昨年8月にWebエンジニアとして就職(転職)したばっかりで、まだまだ業務知識や周辺知識をキャッチアップしていく必要がありました。なので、時間の使い方として、AtCoderに全部のリソースを使うということはできませんでした。1日数問、問題を解くというスタンスで勉強しています。

プログラミングコンテスト AtCoder入門

8月頃、まずこれからやりました。

競プロで有名なけんちょんさんが著者で、ABCのA~Dあたりを対象に書かれています。 A ~ D で必要なアルゴリズムに一通り触れるという意味でとても勉強になりました。サンプルコードがPythonというのもありがたいポイントでRubyかJSしかわからない自分でもなんとなくの雰囲気でRubyに変換しながら理解できたのも大きなポイント。

Difficulty 600以上は飛ばしてやったので、もう1周どこかでやり直したい!

ABCのB埋め

定石ですね。ABC50から現在の問題までは全部解きました。

今年1月から1ヶ月間集中して、B問題を埋めていきました。仕事を終えたあと1時間程度で1日3~4問解いています。 「速く、正確に」解くということを目標にしたので20分で4問を目標にしていました。

残りの時間は解説を読んだり、他の方の回答を見て2,3パターンで実装できるように心がけました。

現在はC問題を埋めている最中です。

その他

鉄則本もやったんですが、難しくて到底全部は出来ず。わかるところだけのつまみ食い状態でした。緑になるためにまたチャレンジしたいです。

Kokura.rbで『問題解決のための「アルゴリズム×数学」が基礎からしっかり身につく本』を読んだのも楽しかったです。

こんな感じでやってます

<Acheivement> AC 555!!!

Acheivement
<Pie Charts>

B問題は大体解いた

AtCoder Pie Charts

<Difficulty Pies>

ほとんど灰色の問題しか解いてません!

Difficulty Pies

今後について

AtCoder界隈のコミュニティに入って入ってみたいなぁという思いがあります。バチャというものをみんなでやってる気がしてて、バチャって何かよくわかってないんですよね、笑

それが気になってます。

色に関しては緑までは絶対に目指そうと思っていますが、緑になったら次は水色、、、とかとか言ってそうで怖い。

2022年にしたこと

2022年ももうすぐ終わるとのことなので、この年の瀬ブームに乗っかって2022年の総括をしておかねば。年末なので比較的ふざけたポエミーな記事になります。

フィヨルドブートキャンプ卒業した

4月にフィヨルドブートキャンプを卒業した。このブログでほぼ毎回出てくる名前なので、よく読んでくれている殊勝な方はご存知だと思う。自分にとってプログラミングの基礎を教えてもらっただけでなく、わからないに立ち向かう姿勢を教えてもらったスクールだ。いい意味で随分と泣かされた。まずね、普通に卒業できない*1。自主休会中も含めたら2年半は在学していた。月額3万円と良心的なスクールなので

3 万 × 12 ヶ月/年 * 2.5年 = ...

おっと、計算はやめておこう。

卒業したから、偉そうな顔で1つだけアドバイスをするとしたら以下の2つになる。

  • (落ち込むくらい)他人と比べない
  • 友人を作る

こうは書いたが、他人と比べないのは個人的には無理だと思っている。比べるよね。普通に。ちょっとぐらい比べながら(致命傷は避けながら)傷つきながら「なにくそー!」根性で頑張った方がいいのかもしれない。ただ、比べてるのは自分で落ち込んでるのも自分なので敵は自分だと自覚する必要がありそう。

友人を作るはほんとにやった方がいい。すごくおすすめ。たとえ、進捗が1ヶ月進まなくなるとしても友達、知り合い、声馴染みの受講生を1人でも作っておくことで離脱率がグッと下がると思う。

今は良い意味で切磋琢磨する友達ができて、働いている今でもよく交流するのでかけがえのない友人と出会えてよかった。

転職した

8月からプログラマーとして働いた。

楽しいか?と問われればもちろん楽しいと答える。だけど、今は仕事することに一生懸命で100%楽しめてるか?と聞かれればそうじゃない感じ。余裕はない状態。そんなことも含めて楽しいっちゃ楽しい。

余裕のなさは自分ならこれくらいはできるだろうという期待値が高いことにあると思う。前職のステータスを全部捨てて未知の領域(IT)に入った。自分でいうのはなんだけど、教員としては結構優秀でいろんな方に認めていただいていた。そういったプライドとか自負が自分の中にあって、同じ基準で仕事をしてしまっている、けどできてないとというのが、余裕のなさとして現れている気がする。

経験上ここら辺のチューニングは1年くらいあれば修正できると思っているので心配はしていない。

悲壮感はない。わからないことも含めて楽しい。

RubyKaigi 2022に参加した

社のご支援承り、RubyKaigi2022に参加した。初めてのオフラインイベント参加。開催地が三重県の津市で家から近かったこともあって、車での日帰りツアーを敢行した。いろんな人に対して、「ほんとに存在するんだ」という感動と、去年オンラインで参加した時よりも話がわかる(わかると言っても2割程度)という感動があって、行った甲斐があった。

来年は長野県松本で開催される。ぜひオフラインでの参加をしたい。

勝手にモブプロを開催した

「勝手にモブプロ」ってなんなの?というのは以下のエントリに説明を譲ることにするが、簡単に説明するとこれは今年立ち上げたAtCoderの問題をモブプロでワイワイいいながら解いていく遊び。(不定期)

haruguchi-yuma.hatenablog.com

今年、2月3日に初めて開催してから31回も開催できた。ひとりぼっちになることもなく31回も開催できたのは、これもひとえに「勝手にモブプロ」をブログに書いてくださったり、宣伝してくださる人のおかげだと思う(@hiromisugie, garammasala29などなど) 。ありがとうございます。今度あったら何かあげたい。物で釣りたい。胡麻もすりたい。

TypeScriptの輪読会を主催した

@Maedaと共にフィヨルドブートキャンプ内でプロを目指す人のためのTypeScript入門の輪読会を主催した。いわゆる共同開催というやつだ。 役割分担はこう。

  • Hack.mdの用意 → @Maeda
  • 当日の開催告知 → @Maeda
  • その他諸々の雑事 → @Maeda
  • 賑やかし → 私

ほぼ何もやってない。主催するってなんだろうね。圧倒的感謝。 現在6章まで読んでいる。興味ある方はぜひ。

AtCoderのコンテストに参加した

10月くらいからコンスタントに参加することにした。 まずは茶色を目指したい。

自転車買った

ここからはプログラミングに関係ないプライベートな話。プライベートの話でこれが一番最初に出てくるのがなんかやばそう。

引きこもりの運動不足解消に自転車を買った。社内でもプライベートでもこの話をすると「何買ったんですか(ロードバイクかな?クロスバイクかな?)」 みたいなことになるのだけど、2万円くらいのただのママチャリを買った。いや、ただではないな!なんと、6速変速機つき!ダイナモライトではなくて勝手に光やつ!すごい!

大学時代は変速機なしのダイナモ付きママチャリでビワイチ*2をしていたのでそれを思えば大幅なパワーアップをしている。就職したてでお金がなかったわけではない。

ジムに行き始めた

前述の通り自転車を買った。買ったけど、、、買ったのは11月ということもあって、とても寒い。人類は寒いと自転車に乗らない。その問題を解決すべく12月からジムに通い始めた。金にものを言わせて健康を買うスタイルを選んだ。健康のために始めたけど、ついでにまっちょになりたい。黒光り筋肉まっちょ。

気になる人は続報を待て!

スプラトゥーンをした

普段あんまりゲームはしないのだけど、スプラトゥーンは1の頃からやっている。9月に3作目が出たので当然買った。必要な出費。これのせいでママチャリになったわけではない。

自宅のよわよわ回線ではなぜか通信エラーを頻繁に起こしてしまうのでバンカラマッチではなくサーモンランばっかりやっている。12月現在バンカラマッチはB-だった(そろそろやるか)。サーモンランは最高500までいった。カンスト頑張ろうとするたびに時間を蝕んでしまうので、11月はサーモンラン禁止令を発動したが12月にまたぼちぼちやってる。来年もやる。

電子レンジが爆発した

12月。深夜、学習のお供に焼きおにぎりをチン*3していたら、突如電子レンジが光り出した。

「ビビビビビビ!」という異音と共に電子レンジ内でちっちゃく爆発した。

びっくりしすぎて、すごいスピードで逃げてしまった。あまりにも突然すぎたので次からは前もって言っておいてほしい。

しばらく電子レンジが使えないという日が続いたが、電子レンジはあまりにも偉大だった。 気づいたら昼ごはんに冷凍唐揚げを皿に持っていたし、冷めたコーヒーを温め直そうと電子レンジを何回も開けてしまった。

18歳の時に一人暮らしをするのでリサイクルショップで3000円で購入した電子レンジ。今32歳なので14年 + α 使ってることになる。流石に寿命だ。今までありがとう。

最後に

2022年も楽しくプログラミングできました。関わってくれた方々ありがとうございます。 2023年にやりたいことは別途書こうと思います。来年もよろしくです。

*1:卒業はできる。できた。

*2:琵琶湖を1周する。エクストリームな修行。ドMがする遊び。

*3:電子レンジで温めること。死語ではないはず。

学びながら遊ぶ、Rubyの楽しみ方(オープンクラスで遊ぶ)

現在12月24日です。明日はRubyにとってすごくおめでたい日ということと、アドベントカレンダー part2が空いていたのでこっそり記事を書いてみたいと思います。

travel_to Time.zone.local(2022, 12 05, 00, 00, 00)

この記事は Rubyのカレンダー Advent Calendar 2022 - Qiitaのpart2 5日目の記事です。

qiita.com

テーマ

初学者なりにこんな風にRubyで(を)楽しんでいるぜ!という一例をお伝えできたらと思っています。

フィヨルドブートキャンプ*1在学中、下記の構文を見てどういう仕組みで動いているのか非常に不思議に思いました。

['a', 'b', 'c'].map(&:upcase)
=> ["A", "B", "C"]

そこでこの構文の仕組みを学習してみようと思って、いろいろ遊んだので紹介したいと思います。 こういうふうに遊びながら学ぶのが私なりのRubyの楽しみ方です!という記事です。

仕組み

ざっくりとした説明は以下の通りです

  1. メソッド呼び出しにおいて、引数の先頭に&がついている場合は、暗黙的にto_procメソッドが呼び出される。
  2. symbolにはto_procメソッドがあり、メソッド内部でproc化され任意のタイミングで呼び出される

オレオレmapメソッドをArrayクラスに定義するとわかりやすいと思って、オープンクラスを使って車輪の再発明をしてみました。

using (Module.new do
  refine Array do
    def oreore_map(&block)
       return to_enum(:map) unless block_given?

      result = []
      each do |obj|
        result << yield(obj)
      end
      result
    end
  end
end)

['a', 'b', 'c'].oreore_map(&:upcase)
=> ["A", "B", "C"]

メソッド定義の中にデバッガを入れてblock.classとしてみたり、いろいろ試すことで挙動を理解することができました。

遊ぶ

to_procが呼び出されることによってprocオブジェクトに変換されているのであれば、to_procが定義されているものであれば何でも渡すことができるのかな?という疑問が湧いてきたので、いろいろ実験してみました。こういう実験が面白いです。

hashにはto_procというメソッドが定義されているようでした。 https://docs.ruby-lang.org/ja/latest/method/Hash/i/to_proc.html

なるほどkeyからvalueへの変換が簡単にできるんですね。

hash = { a: 100, b: 200, c: 300 }
%i(a b c).map(&hash)
=> [100, 200, 300]

Methodクラスにもto_procメソッドが定義されているようです。 https://docs.ruby-lang.org/ja/latest/class/Method.html

method = 123.method(:*)
[1, 2, 3].map(&method)
=> [123, 246, 369]

さらに遊ぶ

いろいろ実験してto_procに応じることができるオブジェクトを&と一緒に渡すことができる。ということがわかりました。 さらにこんな衝動に駆られます。

「シンボルじゃなくて、文字列を渡したい!!!!」

ここまでの実験でto_procメソッドさえ定義したらうまくいけるのではないか?という自信がついていたので(&文字列)を渡せるようにしてみました。 注意: Error処理はしてません

using(Module.new do
    refine String 
      def to_proc
        Proc.new do |*args, $block|
          args.shift.send(self, *args, &block)
        end
      end
    end
end)

'upcase'.to_proc.call('aaa')
=> "AAA"

['a', 'b', 'c'].map(&'upcase')
=> ["A", "B", "C"]

文字列を渡してもうまく動くようになりました!!!

まとめ

このように遊びながら学ぶことでRubyと仲良くなっていく感覚があり、自分なりの楽しみ方になっているので同じ初学者の方などの参考になれば嬉しいです。

travel_back

Ruby.3.2のリリースが楽しみです。(関係者の方ありがとうございます:bow:) Happy Holidays!!

*1:Rubyなどを学べるプログラミングスクール

ためして分かる、N+1問題とその解決方法

この記事はフィヨルドブートキャンプ Part 1 Advent Calendar 2022 の23日目の記事です。

昨日はpart1が penoさんのフルタイムで働く社会人が月に100時間の勉強時間を確保できるようになるまで。私なりの具体的なやり方と失敗事例 - ぺのめも、part2が uchihiroさんのVSCodeのパッケージ不適合によるエラー解決までの道のり(Byebug編)でした。

前振り

フィヨルドブートキャンプを今年の4月に卒業しました。卒業生なので、近況報告などしようかなと思ったんですがDiscordや日報などで日々わーわー言ってるので自重しておきます。

その代わり?最近友達とN + 1 問題の勉強会を行ったのでハンズオン形式で理解できるような記事を書いてみようと思います。(わからないところあったらフィードバックください)

Ruby on Rails でよく発生するN + 1 問題ですが、そもそもN + 1問題とはなんなのか? また、どうやって解決するの?ということが書かれています。(はずです)

今回は複数のモデルが絡んでくるような複雑な話は除いているので、比較的とっつきやすいと思います。ぜひ実際に手を動かして確かめてみてください。(需要があれば複雑バージョンも書くかも)

想定読者

  • フィヨルドブートキャンプ生
  • N + 1 問題 ってなんだろ?と思っている人
  • Railsで用いられるeager loading のメソッド(preload, eager_load, includes)の違いがよくわからない人

環境

Rails 6系、Ruby 2.7系でも動くのを確認しています。

本編

準備

まずはアプリケーションを用意します。N+1_appとでも名付けておきましょう。

$ bin/rails new N+1_app && cd N+1_app
# ファイルの生成が始まる
...
# アプリのルートディレクトリに移動

このアプリでは「ユーザーが何かを投稿するサービス」を模してUserモデルとPostモデルを作成します。話を簡単にするためカラムは最小限にしています。 以下、DB設計の図です。

UsersテーブルとPostsテーブルの関係

上記の通りにモデルを作成しマイグレーションファイルを適用していきます。

# Userモデルの作成
$ bin/rails g model user name:string age:integer

# Post モデルの作成 外部キーの設定をするためにuser:references型を指定
$ bin/rails g model post title:string body:text user:references

$ bin/rails db:migrate
== 20221220231549 CreateUsers: migrating ======================================
-- create_table(:users)
   -> 0.0008s
== 20221220231549 CreateUsers: migrated (0.0008s) =============================

== 20221220232153 CreatePosts: migrating ======================================
-- create_table(:posts)
   -> 0.0011s
== 20221220232153 CreatePosts: migrated (0.0011s) =============================

現在UserモデルとPostモデルは以下のようになっています。

# app/models/post.rb
class Post < ApplicationRecord
  belongs_to :user
end

# app/models/user.rb
class User < ApplicationRecord
end

モデルを作成したときにuser:referencesとしたのでPostモデルの関連付けがbelongs_to :userになっていることがわかります。 一方Userモデルの方には関連付けがまだないので設定していきます。

# app/models/user.rb
class User < ApplicationRecord
  has_many :posts # 追加
end

has_manyに渡す引数は複数形にするのに注意しましょう。

最後は初期データの作成です。rails consoleを使って作成します。 Userを5人、それぞれに対してPostを3件ずつ作成しています。

$ bin/rails c
# rails console (irb) 起動
5.times do |i|
  User.create!(name: "名前 #{i}", age: i + 18) # ここを18にしているのは意味があります
end

User.count
=> 5

User.all.each do |user|
  3.times do |i|
    Post.create!(title: "タイトル #{i}", body: "内容 #{i}", user: user)
  end
end

Post.count
=> 15

これで準備は整いました。

N + 1 問題とは

N + 1 問題とは、DBへの問い合わせ(クエリの実行)をたくさん行ってしまうことによってパフォーマンスが落ちてしまう問題のことです。 一般的にメモリ上にキャッシュされたデータを読み込むのとDBへ問い合わせてデータを読み込むのでは後者の方が時間がかかります。イメージとしては、机の上(メモリ上)にお菓子を置いておくか冷蔵庫(DB)にお菓子を置いておくかで取りに行くスピードが変わる感じです。(この例えで大丈夫かな?) また、「たくさん」とは具体的には N + 1 件ものクエリを発行してしまうことになります。

  • N件のデータをもつテーブルの情報を読み出すのに1件のクエリを発行する
  • 読み出したテーブルに紐づく各データを1件ずつ読み出すのに N 件のクエリを発行する

です。この2つの合計が 1 + N なので N + 1問題といいます。 実際に N + 1 件のクエリを発行してみましょう。

N + 1 に出会う

ここから先はrails consoleを使って確認していきます。

posts = Post.all  # (1)
posts.each do |post|
  puts post.user.name #  (2)
  puts post.title
end

(1)でpostオブジェクトを全件取得しています。

(2)でpost に紐づくuserオブジェクトを取得し、そのnameを表示しています。 post.userを読み込むたびに関連するuserオブジェクトを取得するためクエリが発行されるのがわかります。

Post Load (0.1ms)  SELECT "posts".* FROM "posts" # (1)

 # ----------------------↓以下 (2)  15件SQL文が続く -------------------------------
  User Load (0.1ms)  SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
名前 0                                                                                
タイトル 0                                                                            
  User Load (0.0ms)  SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
名前 0                                                                                
タイトル 1                                                                            
  User Load (0.0ms)  SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
                                        ......... 
                                       ... 略  ...
                                        .........

  User Load (0.0ms)  SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 5], ["LIMIT", 1]]
名前 4
タイトル 1
  User Load (0.0ms)  SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 5], ["LIMIT", 1]]
名前 4
タイトル 2

(1)で1件、(2)で15件、全部で16件のクエリがあります。 一般に、postオブジェクトの数が N 個 ある場合 1 + N 件のクエリが発行されます。(つまりは N + 1 ) オブジェクトの数が少ないときはいいのですが、何万件もに膨れ上がると発行されるクエリの数が膨大になりパフォーマンスに影響をきたします。

これを解決するにはクエリの発行回数を減らす必要があります。

解決編

あらかじめUsersテーブルからもデータを取得しておけば、クエリの発行回数を減らすことができます。この、「あらかじめデータを取得しておく」ことを eager loading というらしいです。 Railsにはeager loadingを実現する方法として3つのメソッドが使用できます。この3つのメソッドは微妙に挙動が違うのでそれぞれ順に確かめていきましょう。

preload

preloadというメソッドがあるので試してみます。 rails consoleで試してみます。

posts = Post.preload(:user)
  Post Load (0.1ms)  SELECT "posts".* FROM "posts" # (1)
  User Load (0.2ms)  SELECT "users".* FROM "users" WHERE "users"."id" IN (?, ?, ?, ?, ?)  [["id", 1], ["id", 2], ["id", 3], ["id", 4], ["id", 5]] # (2)

posts.each do |post|
  puts post.user.name
  puts post.title
end

名前 0
タイトル 0
名前 0
タイトル 1
名前 0
タイトル 2
 ... 略 ...

SQL文に着目すると全部で2件のクエリが発行されていることがわかります。

Post Load (0.1ms)  SELECT "posts".* FROM "posts" # (1)
User Load (0.2ms)  SELECT "users".* FROM "users" WHERE "users"."id" IN (?, ?, ?, ?, ?)  [["id", 1], ["id", 2], ["id", 3], ["id", 4], ["id", 5]] # (2)

(1)でPostテーブルを全件読み込んでいます。(2)ではUserテーブルからあらかじめ必要なデータを1回のクエリでまとめて取得しています。( IN句の部分)

あらかじめ必要なデータとはpostオブジェクトに紐づいているユーザという意味で、例えば投稿が一件もないユーザーがいたとした場合、そのユーザーのデータは取得されません。

$ bin/rails c

User.create!(name: '投稿なし', age: 100)
User.count #=> 6

Post.preload(:user)

Post Load (0.1ms)  SELECT "posts".* FROM "posts"
User Load (0.1ms)  SELECT "users".* FROM "users" WHERE "users"."id" IN (?, ?, ?, ?, ?)  [["id", 1], ["id", 2], ["id", 3], ["id", 4], ["id", 5]]

IDが6である投稿なしのユーザはpostオブジェクトに紐づいてないので取得できていないことがわかります。

このようにpreloadを使うとpostオブジェクトの件数が増えてもクエリが2つに抑えられるのでN + 1問題が解決します。

注意点としてはテーブル同士を結合しているわけではないので関連先のテーブルで絞り込むことはできません。 例えば、`user_id が3以下であるユーザーを取得してみようと絞り込みを行うとエラーが発生します。

Post.preload(:user).where(user: {id: [1, 2, 3]}).each do |post|
  puts post.user.name
end

 SQLite3::SQLException: no such column: user.id (ActiveRecord::StatementInvalid) # エラー1
 no such column: user.id (SQLite3::SQLException) # エラー2    

eager_load

prealodの他にeager_loadというメソッドがあります。eager_loadはテーブル同士を左外部結合し、一つの大きいテーブルにしてメモリ上にキャッシュします。

posts = Post.eager_load(:user)
SQL (0.1ms)  SELECT "posts"."id" AS t0_r0, "posts"."title" AS t0_r1, "posts"."body" AS t0_r2, "posts"."user_id" AS t0_r3, "posts"."created_at" AS t0_r4, "posts"."updated_at" AS t0_r5, "users"."id" AS t1_r0, "users"."name" AS t1_r1, "users"."age" AS t1_r2, "users"."created_at" AS t1_r3, "users"."updated_at" AS t1_r4 FROM "posts" LEFT OUTER JOIN "users" ON "users"."id" = "posts"."user_id" # (1)

posts.each do |post|
  puts post.user.name
  puts post.title
end

名前 0
タイトル 0                                                                                 
名前 0                                                                                     
タイトル 1  
 ... 略 ...

今回はクエリの発行は1件だけになっています。 SQLが少しややこしいので、ちょっと整形してみます。

SQL (0.1ms)
SELECT
  "posts"."id",
  "posts"."title",
  "posts"."body",
  "posts"."user_id",
  "users"."id",
  "users"."name",
  "users"."age",
FROM
  "posts"
  LEFT OUTER JOIN
  "users"
    ON "users"."id" = "posts"."user_id"

created_atupdated_atを省略し、SELECT句にあったAS は別名をつけているだけなので省略しました。これでかなり読みやすいのではないでしょうか? FROM句でデーブルの結合をしていることがわかります。 左外部結合なのでどのテーブル(左)を主としてどのテーブルをくっつけるのかを意識しないと意図しないデータを取得してしまうことがあるので気をつけましょう。

eager_loadの特徴はテーブルを結合しているので関連先のテーブルで絞り込みができることです。

Post.eager_load(:user).where(user: {id: [1, 2, 3]}).each do |post|
  p post.user.name
end

"名前 0" #user_id=1
"名前 0"
"名前 0"
"名前 1" #user_id=2
"名前 1"
"名前 1"
"名前 2" #user_id=3
"名前 2"
"名前 2"

また、mergeメソッドなどでスコープを使って絞り込むこともできます。 下は、20歳以上のユーザーが投稿した、postを取得しています。

# app/models/user.rb
class User < ApplicationRecord
  has_many :posts
  scope :adult, -> { where('age >= ?', 20) } # スコープを定義する
end

$ bin/rails c

Post.eager_load(:user).merge(User.adult)

includes

最後はincludesメソッドです。これはデフォルトの挙動がprealodなのですが、特定の条件を満たした時にeager_loadと同じ挙動になるメソッドです。

# preloadと同じ挙動
Post.includes(:user)
  Post Load (0.1ms)  SELECT "posts".* FROM "posts"
  User Load (0.1ms)  SELECT "users".* FROM "users" WHERE "users"."id" IN (?, ?, ?, ?, ?)  [["id", 1], ["id", 2], ["id", 3], ["id", 4], ["id", 5]]


# eager_loadと同じ挙動
Post.includes(:user).where(user: {id: [1, 2, 3]})
SQL (0.2ms)  SELECT "posts"."id" AS t0_r0, "posts"."title" AS t0_r1, "posts"."body" AS t0_r2, "posts"."user_id" AS t0_r3, "posts"."created_at" AS t0_r4, "posts"."updated_at" AS t0_r5, "user"."id" AS t1_r0, "user"."name" AS t1_r1, "user"."age" AS t1_r2, "user"."created_at" AS t1_r3, "user"."updated_at" AS t1_r4 FROM "posts" LEFT OUTER JOIN "users" "user" ON "user"."id" = "posts"."user_id" WHERE "user"."id" IN (?, ?, ?)  [["id", 1], ["id", 2], ["id", 3]]

基本的に関連先のテーブルで絞り込みを行った時はeager_loadと同じ挙動になるのですが、もう少し細かい条件については以下の記事を参考ください。

まとめ

  • N + 1 問題とはオブジェクトを取得する際に N + 1 件のクエリを発行してしまうという問題
  • 解決方法はクエリの発行回数を減らすことで、取得したいデータをあらかじめメモリ上へキャッシュすれば良い
  • データをキャッシュしておくためのメソッドは3つあり(preload, eager_load, includes)それぞれデータの取得方法が異なる

参考

いかがでしたでしょうか? N + 1 問題について少しでも身近に感じてもらえたらありがたいです。

アドベントカレンダーもいよいよ大詰め!明日はshuji watanabeさんです。楽しみ!

【Ruby】等差数列の配列をつくる

たとえば、3から23までの範囲で、4ずつ増えていく数列の配列を作りたいとする。 数学的にいうと初項3、公差4の数列を23になるまで作りたい こんな感じ。(最後のやつがいいたいくて書いたブログです)

[3, 7, 11, 15, 19, 23]

やり方を色々考えてみる。 環境はRuby3.1です。

Enumerable#selectで絞る

パッと思いつくものはselectメソッドを使って絞り込むというもの。3, 7, 11, ... は4で割って3余る数の集合とみなせるので以下のようにできる。

(3..23).select { _1 % 4 == 3 }

Enumerable#mapで頑張る

(0..5).map { 4 * _1 + 3 }

一般項を求めてmapで変換するけど、これは少し苦しいなー。なぜ0から5とわかるんだと言われたら逆算してるので、、、と答えるしかない。 selectで絞るという方法もあるけど、、、微妙ですね。ボツ。

Enumerator::ArithmeticSequence クラスを使う

僕も初めて知ったんですが、Enumerator::ArithmeticSequenceなる等差数列を扱うクラスがあるみたい。 クラス名長すぎやしませんかね?笑

るりま:https://docs.ruby-lang.org/ja/latest/class/Enumerator=3a=3aArithmeticSequence.html

等差数列オブジェクトを作成するにはstepメソッドを使う

等差数列オブジェクトの生成
as = 3.step(by: 4, to: 23)
=> (3.step(by: 4, to: 23))

as.class
=> Enumerator::ArithmeticSequence

as.to_a
=> [3, 7, 11, 15, 19, 23]

# こうも書ける
as = 3.step(23, by: 4)

Enumerableモジュールを継承(インクルード)しているのでループ処理をするだけならto_aは不要です。

%を使って短く記述することも可能。(これを紹介したかった)

as = (3..23) % 4
=> ((3..23).%(4))

as.class
=> Enumerator::ArithmeticSequence

公差が0の時はエラー

as = (3..23) % 0
=>  `%': step can't be 0 (ArgumentError)

特に%の方は短くかけて便利そうじゃないですか? AtCoderとかで使えそうだなと思いました。

まとめ

(3..23) % 4がかっこいいから紹介したかっただけ。

https://docs.ruby-lang.org/ja/latest/class/Range.html#I_--25