こんにちは! Platform Divisionという部署に所属しているうさみ(@tadsan)といいます!
明日からRubyKaigiですね! ふだんはPHP系のカンファレンスに参加していますが、去年プライベートでふらっと10年ぶりに松山のRubyKaigiに参加したら、次は北海道で開催されるということで連続参加することになってしまいました!
私はふだんPHP系のカンファレンスに参加しているのですがRuby界隈はご無沙汰なので、PHPerなりの視点でRubyKaigiのタイムテーブルを紹介させていただければと思います!
The Journey of Box Building
tagomorisさんによるオープニングキーノートです。
Ruby Boxは昨年12月にリリースされたRuby 4.0で追加された新機能です。リリース前は「Namespace」とも呼ばれていましたが、これはPHPやJavaのような言語にある名前を階層化するための構造ではなく、名前が属する空間そのものを隔離する仕組み(Namespace Separation)です。
「空間を隔離する」とはどういうことでしょうか。
まず、階層化された名前空間が導入される前のPHPは、同じ名前のクラスや関数は、それぞれ一つしか定義することができませんでした。 class Book というクラスは一個しか定義できません。
かつてのPHPでよく見られたのは class Foo_Book や class MyApp_Book のように _ を擬似的な名前空間の階層として利用することです。この方式のデメリットは、もちろん全ての箇所でこの長いクラス名を書く必要があるということです。
これは評判が悪く、使いにくいものでした。現代では「トークンの浪費」と怒られてしまうところです。そこでPHPがとった戦略は「ファイルに名字をつける」という発想です。
PHPでは、ファイルの冒頭に namespace Foo; と書くと、そこで定義された class Book のフルネームは Foo\Book となります。 namespace MyApp; と書かれたファイルで class Book と書けば、 MyApp\Book になりますが、同じ階層に属するファイル間では単に Book とだけ書けばよくなります。
めでたしめでたし… なのですが、これは世界は一つに閉じている場合の話です。現代のWebアプリケーションでは、開発者は単一のアプリケーションを一から書いているわけではありません。
外からは一枚岩として動いているように見えていても、Webアプリケーションフレームワーク・テスティングフレームワーク・複数のライブラリ・プロファイラ・可観測性(o11y)エージェント・モジュラーモノリス、さらにはそれらが再帰的に依存するライブラリといった、様々な出自のものが同じプロセス空間に同居している、呉越同舟の世界です。
PHPの場合は「名字が違って(Foo, MyApp)いれば、同じ名前(Book)は許す」という世界感でしたが、JavaScriptでは別の解決策がとられました。
JavaScriptは基本的には関数単位のレキシカルスコープではあるのですが、昔からブラウザで動作していたJavaScriptではトップレベルで宣言されたものは window オブジェクトを介してグローバルに公開されていました。もちろんこれは関数などを共有する便利な方法ではあったのですが、それよりは公開したくないものが意図せずに白日のもとに晒されてしまう副作用が多く出ていました。
一方でNode.jsのような処理系ではCommonJSという仕様が取り入れられました。これは名前空間がファイル単位で閉じており、外部モジュールで定義されたものは const foo = require('foo.js'); のように参照して、ローカルスコープの名前に閉じ込めるようになりました。これが、ファイルごとに名前空間が分離されているという状態です。現代ではCommonJSに代わってES modulesというより発展的な仕様が普及し、ブラウザ上でも利用できるようになっています。
また、npmのようなパッケージマネージャは、パッケージごとに固有のバージョンへの依存を持つことができました。これにより、パッケージごとに同じライブラリの別のバージョンに依存していたとしても、個別に依存することができます。これも細かい単位での名前空間隔離があってこそのものです。
このような問題は、もちろんPHPでもあります。特にフレームワーク・ライブラリと開発用のツール(RubyでいうとRubocopなど)をComposer(Bundlerに相当)で一括にインストールすると、汎用的なライブラリの依存バージョン差分で、アップデートがとても大変になってしまうのです。(特に、汎用ライブラリがメジャーバージョンアップした直後は足並みが揃わず、大変になります)
そのような状況でPHPではどのような力技での解決が試みられるのかは、以下の記事をご参照ください。
さて、もちろんNode.js/npmのような構造も良いことばかりではありません。パッケージごとに別バージョンへの依存があるとそれぞれインストールされてディスクを浪費する、依存ライブラリに脆弱性があってもアップデート確認がめんどくさい… などといったことがあります。
そのような事情から、近年のnpmでは依存バージョンのコンフリクトがない限りはインストールされたパッケージはフラットに展開されるようになりました。これによって依存関係の全容把握が容易になり、 npm audit fix などのコマンドで一括アップデートの手間も大きく改善されています。
RubyにおいてはRuby Box / Namespace SeparationがRuby本体に取り入れられたことで、今後RubyやRubyGems/Bundlerのエコシステムはどのような選択肢を選び取っていくのでしょうか。わくわくしますね……!
ところでタイトルの「Box Building」って… 箱… 建物… 館……。
When Can You Skip a Test? Tracking Test Impact
Datadog社のAndrey Marchenkoさんによる発表です。動的言語の真髄は高速にテストを回しながら短いフィードバックループを絶え間なく回し続けることだと信じているので、変更の影響を受けるものだけに絞って実行し続けることはとても理に適っています。
「エンジニアリングチームのCI時間を年間 15 万時間以上節約している」とのことですが、うまく使えればpushするまでもなく手元で実行するのにも役立つポテンシャルがあると思うので、とてもわくわくしますね…!
Back to the roots of date
dateライブラリをC実装からPure Ruby実装への移行に取り組んでいる甚六さんによる発表です。PHPやRubyにおいて、ライブラリをスクリプト言語で書くか、C言語で書くかといった選択は悩ましいものがあります。
PHPでは言語組み込みの関数はPHP本体の一部としてC言語で、ユーザー定義のライブラリはPHP_でComposerでパッケージとして配布し、その中でも独自プロトコルによる外部通信や高速なバイナリ操作を要するライブラリはC言語などで書いた拡張モジュールとしてロードすることに棲み分けされています。
これにはRailsのUnicornやPumaのような常駐するアプリケーションサーバーの同一プロセスでユーザーリクエストを順次処理し続けるモデルとは異なり、PHPはpreforkされたワーカープロセスでリクエストを処理するごとに全ての状態を破棄して、次のリクエストを処理する際に一からロードしなおすという、往年のCGIのような実行モデルが維持されています。
ここでいう「全ての状態」には、グローバル変数のようなものだけではなく、フレームワークが提供するものを含むすべての関数・クラスが含まれています。PHPプロセスではリクエストごとに破壊と再生が繰り返されていると言えます。
そのような実行モデルの是非はさておいて、C言語の動的ライブラリ(shared library)としてロードされたものがあるとpreforkされたプロセスでアンロードできないという問題があることから、PHPにも動的ライブラリをロードする機能(dl()関数)は存在しながらも、基本的に使われず、必要に迫られない限りPure PHPで書くという文化が他の言語よりも徹底されています。
たとえばRubyなどではYAMLのパースには標準のYAMLの代替としてlibyamlベースのPsychが定義されているところ、PHPにも拡張ライブラリで書かれたext-yamlモジュールよりもSymfony\Yamlを使う方が一般的です。
C言語ではなくスクリプト言語で書くということは、名前でC言語を書くよりもメモリ安全である、Cのビルドが不要なのでWindowsはもとよりさまざまな環境にインストールしやすい、Cのような別言語の知識が不要なので貢献を受け入れやすいなど、さまざまな恩恵があります。
そもそもYAMLを読むというシチュエーションが頻回ではないので、スクリプト言語で書いてインタプリタで実行したとしても全体のボトルネックにはならず、問題ないのだとされてきました。
21世紀、2026年のRubyでは時代がもうひとつ先に進んでいるのだと聞き及んでいます。かつては「遅すぎるならCで書き直せ」とされてきました(これはRubyだけでなく、ほかの動的言語でも一般的な手法でした)が、JITによる実行時最適化によってC拡張でチューニングするよりRubyで書いた方が早くなるという状況にすらなっています。
このような背景を念頭におくと、甚六さんが非常に興味深い取り組みをなさっていることがおわかりいただけますでしょうか…!
Liberating Ruby's Parser from Lexer Hacks
近年のRubyで顕著な活動のひとつとして、構文解析についての技術革新があります。
その大きな成果の一つとしてLramaがあり、これは構文の規則を書くと、それを解析する解析器を出力するパーサージェネレータというものにあたります。歴史的にはYaccやGNU bisonというソフトウェアがありますが、LramaはそれらのRuby特化版といえるものです。
このセッションの発表者のydahさんはそのLramaの開発者メンバーの一人で、専門はもちろんRubyの処理系ですが、PHPカンファレンス関西でも『構文解析器入門』の発表をされたことがあります。
RubyやPHPといった言語は、人間が書いたソースコードをスクリプト(台本)として読み込んでプログラムを実行するという特徴があります。C言語やGo、Rustといった言語がソースコードに基いて生成した実行ファイルを起動するのに対して、(省略されることはあるものの)基本的に実行時に毎回構文解析からやり直す必要があるため、非常に重要な要素です。
スクリプトが実行されるまでの流れはydahさんのPHPカンファレンス関西での発表で端的に紹介されています。

(構文解析器入門 - Speaker Deck より引用)
$a = 1; if ($a) echo $a; else var_dump('エラー') というPHPコードが実行される流れを考えてみましょう。
まず最初の段階として、以下のような一次元の配列に切り刻みます。
// 見やすさのために改行していますが、1つの配列です
[
'$a', '=', '1', ';',
'if', '(', '$a', ')', 'echo', '$a', ';',
'else', 'var_dump', '(', '\"エラー\"', ')', ';'
]
これは字句解析と呼ばれるプロセスで、それを行う処理は字句解析器(Lexical Analyzer)、レキサー(Lexer)、トークナイザ(Tokenizer)と呼ばれ、切り刻まれた塊(↑での一個の文字列の単位)を「トークン」と呼びます。最近だと生成AIの文脈でも「トークン」というキーワードが出てきますが、これもだいたい同じようなものです。コードの文字を先頭から読み取る処理になるので、スキャナ(Scanner)とも呼ばれます。
PHPではこのトークナイズ処理は非常に簡単です。基本的には塊ごとに切り出していくだけでいいからです。切り刻むだけでなく「if はキーワード(予約語)」「var_dump はシンボル」のようなラベル付けもセットで行われますが、一旦置いておきます。
気をつけるべきところは "エラー" のような文字列くらいで、それも基本的には「ダブルクォートが出てきたら、次のダブルクォートまで塊として一気に扱う」「/ は割り算とコメント// があるので気をつける」くらいで済みます。
(ほかにはHeredoc/Nowdocや __halt_compiler() というものもありますが… まったくもって些事です)
プログラムをデータとして処理するには一次元の配列のままでまとまりがわからなくて大変なので、構造ごとに分解してデータを詰め替えます。
# これは構造をわかりやすくするために単純化したもの(S式)です
[
['assign',
'var' => ['variable' 'a'],
'value' => ['int', '1'],],
['if',
'condition' => ['variable', 'a'],
'then' => [
['echo', ['variable', 'a']],],
'else' => [
['call',
'function' => 'var_dump',
'arguments' => [
['string', 'エラー']]]]
],
]
ここまで変形できればインタプリタで実行する準備は整ったと言えるのですが、PHPではさらに命令列(opcode)にコンパイルします。
line #* E I O op operands
----------------------------------------
1 0 E > ASSIGN !0, 1
1 > JMPZ !0, ->4
2 > ECHO !0
3 > JMP ->7
4 > INIT_FCALL 'var_dump'
5 SEND_VAL 'エラー'
6 DO_ICALL
7 > > RETURN 1
PHPの処理系であるZendVMはこのような命令にコンパイルした状態で実行するのですが、非常に非常に大雑把にいうと、Rubyもまったく同じことをやっています。
どちらの言語も(PythonやJavaScriptを含めても)やるべきことは大雑把に「同じ」と言ってしまっていいのですが、Rubyの場合はそれでは済まない要素がたくさんたくさんあります。PHPではシンプルだった「切り刻む」(トークナイズ)という処理も、Rubyの世界では考えることが無数に出てきます。
プログラミング言語を解釈することの難しさのひとつが「再帰的」であること、つまり、ある構造の中に同じ種類の構造が何回でも登場しうるということです。
たとえば、関数呼び出し f() には引数として f(g()) と書けるし、 f(g(h()), i()) も書けます。配列 [1, 2, 3] は、 [1, [2, 2, [3, 3, 3]]] のようなものも作れます。もちろんこれらを組み合わせて [1, f([2, g(3)])] のようなものも書けます。
このように複数のものをレゴブロックのように組み合わせていくのが普段われわれが取り組んでいるプログラミングという営みそのものだと言えますが、さきほどの関数や配列がネストしているだけなら、まだ [](), を目印に切り刻んでいけばいいので事情は簡単です。
print "私は「Don't say \"lazy\"」が好きです"; というソースコードを切り刻むことを考えると、「" から読み始めて次の \" はエスケープされているので飛ばす、次も同じく飛ばして、好きですの次の " で文字列の塊はおしまい」と解釈できます。
しかし、Rubyでは print "私は「#{song}」が好きです" のように書くことができます。ということは、print "私は「#{song.upcase}」が好きです" も書けるし、print "私は「#{song.split(" ").map(&:capitalize).join(" ")}」が好きです" も書けるし、 print "私は「#{/[a-zA-Z]+|\d+|\s+|[^\sa-zA-Z\d]+/.scan(song).map{ |s| s.capitalize }}」が好きです" も書けます。
Rubyでは "” を使った文字列リテラルの他にも、%w記法、ヒアドキュメント、正規表現リテラルといった数々の構文に式埋め込みができます。もちろん、式埋め込みの中には式埋め込みができます。何を言っているのかわからないかもしれませんが、Rubyとかいうけしからん言語では、式としてifやcaseだけでなく、classやdefすらも埋め込めます。
ここに埋め込めるコードの種類について、Rubyではほぼほぼ制限は存在しないに等しいのです。そんな状況下で、Rubyは「いま自分はどこにいるのか」という情報を保持し、管理し続けなければいけないのです。それも単なるスタックではなく、複雑な状態遷移を…………。
実はPHPも文字列の中に文字列を埋め込むことは可能なのですが、Rubyと比べると書ける構文の種類は極めて限定的です。……RubyとPHPが戦っている戦場の違いを、おわかりいただけましたでしょうか。
長い前提になってしまいましたが、セッション内容紹介文を改めて見てみましょう。
In traditional LR parsing, the lexer must decide what token to return before the parser sees it. But for context-sensitive syntax, the correct tokenization depends on context that only the parser knows. The lexer can't see it—so Ruby's lexer resorts to manual lex state management, guessing context through hand-written code.
従来のLR構文解析では、レキサーはパーサーがトークンを見る前に、どのトークンを返すかを決定する必要があります。しかし、コンテキスト依存構文の場合、正しいトークン化はパーサーだけが知っているコンテキストに依存します。レキサーはそれを見ることができないため、Rubyのレキサーは手動での字句状態管理に頼り、手書きのコードでコンテキストを推測します。
これは、ここまで紹介したような「状態」を説明しています。PHPでは教科書的なフローで、字句解析と構文解析を独立して処理できました。
Rubyにおいては字句解析処理と構文解析処理が相互に絡み合った、たいへんエキサイティングな構文解析処理を行っています。
続いて後半部分を見てみましょう:
This scatters language rules across implementation details, makes bugs hard to reproduce, and complicates every syntax change. PSLR(1): Pseudo-Scannerless Minimal LR(1), which I've implemented in Lrama, inverts this relationship: the parser tells the lexer which tokens are valid at each point. In this talk, I'll show how PSLR(1) bridges the information gap between parser and lexer, and explore how it could liberate Ruby's parser from the Lexer Hack curse.
これにより、言語ルールが実装の詳細に分散し、バグの再現が困難になり、構文の変更が複雑になります。私がLramaで実装したPSLR(1): Pseudo-Scannerless Minimal LR(1)は、この関係を逆転させます。つまり、パーサーが各ポイントでどのトークンが有効かをレキサーに伝えます。この講演では、PSLR(1)がパーサーとレキサー間の情報ギャップをどのように埋めるかを示し、RubyのパーサーをLexer Hackの呪縛からどのように解放できるかを探ります。
ydahさんたちが取り組んでいるLramaは「解析器を出力するパーサージェネレータ」というものですが、これまで使われている字句解析処理部分は手書きされたコードで処理されています。これをLramaで包括的に処理するための手法のようです。
私は構文解析にはずっと苦手を持ち続けているものの、むかしRubyのparse.yをこねこね弄って遊んでいたことがあり(注: むかしの私は {foo: 1,[改行] bar: 2} のような構文の , が邪魔だなと思っていた(いまでもちょっと思っている)ので、単に改行すればハッシュのレコード区切りできるように実装しました。うっかりパッチを送ってマージされなくてよかったぜ)、このあたりの大変さは骨身に染みているので、発表内容が非常に楽しみです。
Exploring RuboCop with MCP
RuboCopはRubyの静的解析ツールで、単純な文字列マッチではなく構文木をベースにソースコードを分析できるツールです。koicさんはRubocopのコミッターを務め、アクティブに活動されています。
ただスタイルの不一致を検出して警告するだけではなくその修正機能も備えており、RuboCopのルール(Cop)は構文木を操作することでソースコードを編集することもできます。
RuboCopのオリジナル開発者のBozhidar Batsov(bbatsov)はRuby以上にEmacsやClojureハッカーとしても著名でして、Ruby Style Guide、The Emacs Lisp Style Guide、The Clojure Style Guideという3つの言語でコミュニティに受け入れられたデファクト標準になっているスタイルガイドの著者でもあります。個人的にはEmacsのFlyCheckでお世話になっています。
さておき、私はコードフォーマットやリファクタリングにも強い興味がありまして、PHPではPER Coding Styleというものに関心を持っています。良いコードスタイルとは何でしょうか?
PSRとコーディングスタイルの関係、そしてPERへ - Qiita
このルールの前文に以下のような記述があります。
Like PSR-12, the intent of this specification is to reduce cognitive friction when scanning code from different authors. It does so by enumerating a shared set of rules and expectations about how to format PHP code. This PSR seeks to provide a set that coding style tools can implement, projects can declare adherence to and developers can easily relate to between different projects. When various authors collaborate across multiple projects, it helps to have one set of guidelines to be used among all those projects. Thus, the benefit of this guide is not in the rules themselves but the sharing of those rules.
PSR-12と同様に、本仕様の目的は、異なる作者によるコードを読む際の認知的摩擦(認知的負荷)を軽減することです。これは、PHPコードのフォーマット方法に関する、共有のルールと期待される事項を列挙することによって実現されます。
本PSRは、コーディングスタイルツールが実装でき、プロジェクトが準拠を宣言でき、そして開発者が異なるプロジェクト間でも容易に馴染むことができるような、一連のルールを提供することを目指しています。様々な作者が複数のプロジェクトにまたがって共同作業を行う場合、それらすべてのプロジェクトで共通して使用できる1つのガイドラインがあると非常に役立ちます。
したがって、このガイドの利点はルールそのものにあるのではなく、そのルールを共有するという点にあるのです。
ここには二つの大事なことが書いてあります。
つまり、「世の中にある全てのプロジェクトにベストプラクティスである厳密な規約を守らせることではなく、コードを読む際の認知的摩擦を軽減するのが目的」だということ、「中立的なスタイルをベースラインとしてプロジェクト固有のスタイルを策定したり、フォーマットツールに組み込まれたデフォルトスタイルからの差分で設定できるのは非常に便利」だということです。
さて、コーディングエージェントの活用が隆盛している昨今ですが、コード中の些細なスタイルを編集するために生成AIに処理させることはトークンの無駄遣いもいいところです。それは些細なコード編集においてもそうです。LLM(大規模言語モデル)は名前の通り「言語」として認識・処理することに特化しています。理想でいうと、編集対象のソースコードも純粋なデータ構造として理解し、シンプルなDSLを元に構文木を間違いなく処理して、AIさんには些事に気をとられてほしくないところです。
私も最近はそんなことを考えながら暮らしているので、RuboCopとMCPのアプローチにはとても興味があります!
Digits, Digits, and Digits
Rubyで変なコードを書くことに定評があり、コミッタとしても活躍しているぺん!(@tompng) さんの発表です。
あなたのお使いの言語は「数」をどのように扱いますか? データとしての数を扱うための基本的な戦略は大別すると「データサイズを決めて、収まる範囲の数を扱う」「必要なだけのデータ量を使ってその数を扱う」です。
では、われらがPHPはどうしているのでしょうか。
PHPに限らず多くのプログラミング言語においては、基本的な数は固定長、つまり一定のバイト数の整数または浮動小数点数として扱っています。PHPには PHP_INT_MAX と PHP_INT_MIN という定数があり、その範囲に収まる数を int 型として扱っています。ではPHP_INT_MAX + 1 とか ((PHP_INT_MAX / 2) + 1) * 2 のような PHP_INT_MAX の範囲を越える演算を行うとどうなるでしょうか。
このあたりをどう扱うかが言語によってポリシーの大きく異なるところです。
PHPは PHP_INT_MAX + 1 は必ずfloatになります。一方でRubyには基本の Integer クラスには一般的には上限はありません。その代り、Rubyの内部で値を表わすVALUEという型の値は、タグ付きポインタというビット単位のデータ構造で、C言語レベルでは単一の型でありながらRubyで扱う様々な型を統一的に取り扱えます。
これはトレードオフであり、Rubyでは64bit環境でも整数表現のために64bit空間の全てを使うことはできず、固定長整数は63bitだけで表現するようにしています。基本的な構造は「Rubyソースコード完全解説 第2章 オブジェクト」で「小さな整数」として紹介されているので、ぜひお読みください。
日常的な用途では64bitだろうと63bitだろうとあまり大きな違いはないのですが、RubyとPHPの違いが出てくるのはここからです。Rubyは固定長の整数で収まる範囲(63bit)を越えると、内部表現は自動で多倍長整数に切り替わるようになっています。以前のRubyは実際に BigNum FixNum という別クラスでしたが、現在では Integer クラスに統一されました。
一方で、プログラミングで扱いたい数は整数だけではありません。整数で割り切れない、1以下の端数も表現しなければならなくなることも非常に多くあります。固定長バイトで小数を表すために非常によく使われるデータ構造としては、さきほど名前を出した浮動小数点数(floating number, Float)があります。これは指数部と仮数部を分けて表現することで、精度の概念を持ってより幅広い範囲の数を取り扱えるものです。
このfloatにも問題があります。小数を2進数で表すと、人間が認識している10進数でキリのいい数も端数になってしまうのです。人間が扱っているお金などは10進数を基準にしているので、浮動小数点数で処理をすると意図していない誤差が出てしまうことがあります。
例としては人間には「キリのいい数」にしか見えない 0.1 や 0.3 のような数は、普通の浮動小数点数(float)ではまったくキリのいい数ではありません。
そのような数を扱うために必要なのは10進数保持したい精度を確実に残しながら計算できるデータ構造、つまり任意精度十進数です。以前のPHPではそのようなデータ構造はなかったので、BC関数と呼ばれるものに文字列を渡す必要がありました。
2024年末にリリースされたPHP 8.4では高町さんという方の提案で BCMath というクラスが提案・実装されました。内部的にはBCD(2進化10進数)というデータ構造で保持され、文字列を介さずに入出力できるように改善されました。
BCMathを高速化した一部始終をC言語でガチ目に解説する / BCMath performance improvement explanation
CPUが扱える計算は基本的に固定サイズの整数または小数演算なので、任意精度十進数からいい感じに「桁をずらしながら」扱うことが必要になるはずです。Rubyの BigDecimal も同じ目的を持ったクラスで、まだ実装を見ていないのでアプローチは違うかもしれませんが、同じようなことが必要になるはずです。Digits, Digits, and Digitsです。
まとめ
RubyとPHPはWebという同じ文脈で使われることがあるものの、全然別のコンセプトを持って設計された言語です。しかしながら、課題観として共有できるものや、全然違う言語なりにPHPという補助線を引くことでわかりやすくなったものもあるのではないでしょうか。
隣の芝は青いと申しまして、別の言語のエコシステムはよく見えるもの、逆に自分たちが使い馴染んでいるものが一番実用的でよくできているように感じるものも多くあります。自分が専門ではないカンファレンスに参加するということは、自分の認識を相対化する効果があります。
RubyKaigiはRubyという言語の最前線でエコシステムを改革している人たちの自慢大会です。仕事でRubyを使っていても自分たちは最前線にはいないという方でも、そのような場に飛び込むことで自分の認識を再確認できるのではないでしょうか。
実際のところ、RubyKaigiほど言語処理系や周辺エコシステムで活動している人が一同に会する場はあまり多くありません。PHPでもPythonでも、そのほかの言語でもです。そのような稀有な場であるRubyKaigiを楽しみましょう!
