@ledsun blog 無味の味は佳境に入らざればすなわち知れず 2024-03-14T21:13:51+09:00 ledsun Hatena::Blog hatenablog://blog/12704346814673850113 orbital ring その1 hatenablog://entry/6801883189090757763 2024-03-14T21:13:51+09:00 2024-03-14T21:13:51+09:00 ruby.wasmを使ってRubyでフロントエンドフレームワークをつくったらどうなるかを考えてみましょう。 テンプレートエンジンはERBを使うと良さそうです。 イベントハンドラーのバインドはRailsのルーティングっぽくなる良さそうです。 次のイメージです。 OrbitalRing.routes.draw do click ".ok_button", to: "task.done" ... end アプリケーションのレンダリング先のHTMLElementのidはangular風にapp_root固定にしましょう。 たぶんこんな感じで初期化します。 RubyスクリプトでOrbitarRing.i… <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/ruby">ruby</a>.wasmを使って<a class="keyword" href="https://d.hatena.ne.jp/keyword/Ruby">Ruby</a>でフロントエンド<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D5%A5%EC%A1%BC%A5%E0%A5%EF%A1%BC%A5%AF">フレームワーク</a>をつくったらどうなるかを考えてみましょう。</p> <p>テンプレートエンジンはERBを使うと良さそうです。</p> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%D9%A5%F3%A5%C8%A5%CF%A5%F3%A5%C9%A5%E9">イベントハンドラ</a>ーのバインドは<a class="keyword" href="https://d.hatena.ne.jp/keyword/Rails">Rails</a>のルーティングっぽくなる良さそうです。 次のイメージです。</p> <pre class="code lang-ruby" data-lang="ruby" data-unlink><span class="synType">OrbitalRing</span>.routes.draw <span class="synStatement">do</span> click <span class="synSpecial">&quot;</span><span class="synConstant">.ok_button</span><span class="synSpecial">&quot;</span>, <span class="synConstant">to</span>: <span class="synSpecial">&quot;</span><span class="synConstant">task.done</span><span class="synSpecial">&quot;</span> ... <span class="synStatement">end</span> </pre> <p>アプリケーションの<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%EC%A5%F3%A5%C0%A5%EA%A5%F3%A5%B0">レンダリング</a>先のHTMLElementのidはangular風に<code>app_root</code>固定にしましょう。</p> <p>たぶんこんな感じで初期化します。</p> <ol> <li><a class="keyword" href="https://d.hatena.ne.jp/keyword/Ruby">Ruby</a><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%EA%A5%D7%A5%C8">スクリプト</a>で<code>OrbitarRing.initialize</code>を呼ぶ</li> <li><code>OrbitarRing.initialize</code>は<code>Layout</code>を継承したクラスの<code>new("#app_root")</code>を呼ぶ</li> <li><code>Layout#new</code>はクラス内に定義されたテンプレートを呼び出す</li> </ol> <pre class="code lang-ruby" data-lang="ruby" data-unlink><span class="synPreProc">def</span> <span class="synIdentifier">initialize</span>(selector) <span class="synType">JS</span>.global.querySelector(selector)[<span class="synConstant">:innerHTML</span>] = template.result <span class="synPreProc">end</span> <span class="synPreProc">def</span> <span class="synIdentifier">template</span> <span class="synType">ERB</span>.new(&lt;&lt;~<span class="synSpecial">'END_HTML'</span>) <span class="synConstant"> &lt;%= Task.reder_all %&gt;</span> <span class="synConstant"> </span><span class="synSpecial">END_HTML</span> <span class="synPreProc">end</span> </pre> <pre class="code lang-ruby" data-lang="ruby" data-unlink><span class="synPreProc">class</span> <span class="synType">Task</span> <span class="synPreProc">def</span> <span class="synConstant">self</span>.<span class="synIdentifier">render_all</span> template_all.result_with_hash <span class="synConstant">elements</span>: all <span class="synPreProc">end</span> <span class="synPreProc">def</span> <span class="synConstant">self</span>.<span class="synIdentifier">template_all</span> <span class="synType">ERB</span>.new(&lt;&lt;~<span class="synSpecial">'END_HTML'</span>) <span class="synConstant"> &lt;ul class=&quot;tasks&quot;&gt;</span> <span class="synConstant"> &lt;% elements.each do %&gt;</span> <span class="synConstant"> &lt;%= _1.render %&gt;</span> <span class="synConstant"> &lt;% end %&gt;</span> <span class="synConstant"> &lt;/ul&gt;</span> <span class="synConstant"> </span><span class="synSpecial">END_HTML</span> <span class="synPreProc">end</span> <span class="synPreProc">def</span> <span class="synIdentifier">reder</span> template.result_with_hash <span class="synConstant">element</span>: <span class="synConstant">self</span> <span class="synPreProc">end</span> <span class="synPreProc">def</span> <span class="synIdentifier">template</span> <span class="synType">ERB</span>.new(&lt;&lt;~<span class="synSpecial">'END_HTML'</span>) <span class="synConstant"> &lt;li&gt;</span> <span class="synConstant"> &lt;%= element.name %&gt;</span> <span class="synConstant"> &lt;button class=&quot;ok_button&quot; data-id=&quot;&lt;%=id%&gt;&quot;&gt;</span> <span class="synConstant"> &lt;/li&gt;</span> <span class="synConstant"> </span><span class="synSpecial">END_HTML</span> <span class="synPreProc">end</span> <span class="synPreProc">def</span> <span class="synConstant">self</span>.<span class="synIdentifier">done</span>(event) task = find_by(event[<span class="synConstant">:target</span>]{<span class="synConstant">:dataset</span>][<span class="synConstant">:id</span>]) task.done end <span class="synPreProc">def</span> <span class="synIdentifier">done</span> is_done = <span class="synConstant">true</span> <span class="synPreProc">end</span> end </pre> <p>ERBを良い感じに埋め込む方法ほしいですね。</p> <p>Task<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%F3%A5%B9">インスタンス</a>を自動的に見つけて欲しいです。 何か良い感じのIDの埋め込み方が要りそうです。</p> <ol> <li><code>OrbitarRing.initialize</code>はなんか良い感じで<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%D9%A5%F3%A5%C8%A5%CF%A5%F3%A5%C9%A5%E9">イベントハンドラ</a>ーをバインドする。</li> </ol> ledsun マネジメント入門 その2 hatenablog://entry/6801883189086336690 2024-02-26T20:42:25+09:00 2024-02-26T20:42:25+09:00 できるリーダーは、「これ」しかやらない メンバーが自ら動き出す「任せ方」のコツ作者:伊庭 正康PHP研究所Amazon マネジメント入門 その1 - @ledsun blog につづいて 管理職必読 順番に読むと理解が深まる「マネジメントの名著」11冊 | 日経BOOKプラス で紹介されていた本の二冊名です。 1冊目同様、部長、課長くらいの日本企業の中間管理職向けの本です。 1冊目と比べると具体的な話が少なめでした。 1冊目で大枠をつかんで、2冊目で引き出しを増やす感じでしょうか。 「これしかやらない」というタイトルとは、あまり一致していないのかもしれません。 印象深かった内容 部下は、数値目… <p><div class="hatena-asin-detail"><a href="https://www.amazon.co.jp/dp/B07N77J3CG?tag=ledsun-22&amp;linkCode=osi&amp;th=1&amp;psc=1" class="hatena-asin-detail-image-link" target="_blank" rel="noopener"><img src="https://m.media-amazon.com/images/I/41ZlI3YFTHL._SL500_.jpg" class="hatena-asin-detail-image" alt="できるリーダーは、「これ」しかやらない メンバーが自ら動き出す「任せ方」のコツ" title="できるリーダーは、「これ」しかやらない メンバーが自ら動き出す「任せ方」のコツ"></a><div class="hatena-asin-detail-info"><p class="hatena-asin-detail-title"><a href="https://www.amazon.co.jp/dp/B07N77J3CG?tag=ledsun-22&amp;linkCode=osi&amp;th=1&amp;psc=1" target="_blank" rel="noopener">できるリーダーは、「これ」しかやらない メンバーが自ら動き出す「任せ方」のコツ</a></p><ul class="hatena-asin-detail-meta"><li><span class="hatena-asin-detail-label">作者:</span><a href="https://d.hatena.ne.jp/keyword/%B0%CB%C4%ED%20%C0%B5%B9%AF" class="keyword">伊庭 正康</a></li><li><a class="keyword" href="https://d.hatena.ne.jp/keyword/PHP%B8%A6%B5%E6%BD%EA">PHP研究所</a></li></ul><a href="https://www.amazon.co.jp/dp/B07N77J3CG?tag=ledsun-22&amp;linkCode=osi&amp;th=1&amp;psc=1" class="asin-detail-buy" target="_blank" rel="noopener">Amazon</a></div></div></p> <p><a href="https://ledsun.hatenablog.com/entry/2024/02/25/151026">&#x30DE;&#x30CD;&#x30B8;&#x30E1;&#x30F3;&#x30C8;&#x5165;&#x9580; &#x305D;&#x306E;1 - @ledsun blog</a> につづいて <a href="https://bookplus.nikkei.com/atcl/column/011000181/021600009/">&#x7BA1;&#x7406;&#x8077;&#x5FC5;&#x8AAD; &#x9806;&#x756A;&#x306B;&#x8AAD;&#x3080;&#x3068;&#x7406;&#x89E3;&#x304C;&#x6DF1;&#x307E;&#x308B;&#x300C;&#x30DE;&#x30CD;&#x30B8;&#x30E1;&#x30F3;&#x30C8;&#x306E;&#x540D;&#x8457;&#x300D;11&#x518A; | &#x65E5;&#x7D4C;BOOK&#x30D7;&#x30E9;&#x30B9;</a> で紹介されていた本の二冊名です。</p> <p>1冊目同様、部長、課長くらいの日本企業の中間管理職向けの本です。 1冊目と比べると具体的な話が少なめでした。 1冊目で大枠をつかんで、2冊目で引き出しを増やす感じでしょうか。 「これしかやらない」というタイトルとは、あまり一致していないのかもしれません。</p> <p>印象深かった内容</p> <ul> <li>部下は、数値目標よりも、それで得られるものを語って欲しい。</li> <li>最初の3年間に厳しい仕事を任せないと、4年目以降の成長が鈍る</li> <li>丁寧な指示のしかた <ul> <li>なぜその業務を頼むのか伝える</li> <li>具体的な手順を伝える</li> <li>指示を聞いてどう思ったかを確認する</li> <li>不安な点、不明な点がないか確認する</li> </ul> </li> <li>任せる上司は部下の「不便・不安・不満」を事実で答えられる</li> <li>「やらされた仕事」では成長できない <ul> <li>失敗した言い訳を他責にする</li> <li>工夫できない</li> </ul> </li> <li>心が折られた人は、仕事を任せて結果を出させながら、折々の会話の中で他の視点があることも伝えて行く</li> <li>面白さを教えるのではなく、面白くする方法を教える <ul> <li>仕事の流儀、譲れないこだわりを伝えると、「仕事を面白くする方法」も伝わる</li> </ul> </li> <li>いい人より格好良い人であること <ul> <li>社外活動が充実している上司は、部下から魅力的にみえる</li> </ul> </li> <li>上司が部下の未来に関心を持っているか? <ul> <li>将来の夢、やりたいこと、なりたいものを話し合う機会があるか?</li> </ul> </li> <li>優秀な若者は「今の給与以上に成長できること」を報酬と考える</li> <li>普通に生活しているだけでは「自分はどうしたい」を考えない。 <ul> <li>気がつくきっかけを上司が与える</li> <li>仕事で大事したい価値観を5つあげてもらう。その中の1位を選んでもらう</li> <li>価値観の背景を聞く</li> </ul> </li> <li>チームのNo.2が賛成すると、合意が早い</li> <li>感謝する機会を計画的に仕込んでおく</li> <li>失敗を恐れる人と恐れない人の差は、勇気の差でなく、見ている期間の差</li> <li>やらないことを決める。それが経営だ</li> <li>賛成してくれる2割の意見を聞きながら、進め方を決め、6割に役割を与えながら巻き込んでいく</li> <li>孤独を感じたら、本に答えを求める</li> <li>多様な人たちとの接点を通じて、それぞれの価値観を自分の価値観としてインストールする経験があるとよい</li> </ul> <p>「自分がどうしたい」を考えないとか、「仕事を面白くする方法を知らない」とか、驚きです。 そういう人が居るのは知っていました。 そういう人を導いていくことがマネジメントに含まれるとは思っていませんでした。</p> <p>エンジニアコミュニティにはそういう人少ないのです。 創立20年くらいの小企業にも少ないです。 いや、そうではなく、そういう人達は生き延びることができなくて退場していきます。</p> <p>言われてみれば「そういう人達をモチベートして、退場しないで戦力にできる。戦力にできたら強い組織になる」のは、そらそうだなっと思いました。 選ばれた特質を既に備えている人を選別するのではなくて、備えられるように育てる。 そら、まっとうな話ですね。</p> <p>2冊読んで2冊とも似たようなことが書いてあるので、まあ、そうなんだろうなって思います。 まじかあ・・・</p> ledsun マネジメント入門 その1 hatenablog://entry/6801883189085830694 2024-02-25T15:10:26+09:00 2024-02-25T15:10:26+09:00 離職率ゼロ!部下が辞めない1on1ミーティング!作者:竹野潤自由国民社Amazon 管理職必読 順番に読むと理解が深まる「マネジメントの名著」11冊 | 日経BOOKプラス で紹介されていた本です。 とりあえず一冊目を読んでみました。 マネジメントの本は経営者からチームリーダー向けまでターゲットがわかりにくいことがあります。 この本は、部長、課長くらいの日本企業の中間管理職向けの本です。 1 on 1の話は3章からはじまります。 1~2章をつかってマネージャーの仕事を説明しています。 100ページくらいにコンパクトにまとまっていてわかりやすかったです。 1 on 1 の具体的なハウツーは3章に… <p><div class="hatena-asin-detail"><a href="https://www.amazon.co.jp/dp/B0CMQCMQJN?tag=ledsun-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" class="hatena-asin-detail-image-link" target="_blank" rel="noopener"><img src="https://m.media-amazon.com/images/I/41OYpFV72rL._SL500_.jpg" class="hatena-asin-detail-image" alt="離職率ゼロ!部下が辞めない1on1ミーティング!" title="離職率ゼロ!部下が辞めない1on1ミーティング!"></a><div class="hatena-asin-detail-info"><p class="hatena-asin-detail-title"><a href="https://www.amazon.co.jp/dp/B0CMQCMQJN?tag=ledsun-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" target="_blank" rel="noopener">離職率ゼロ!部下が辞めない1on1ミーティング!</a></p><ul class="hatena-asin-detail-meta"><li><span class="hatena-asin-detail-label">作者:</span><a href="https://d.hatena.ne.jp/keyword/%C3%DD%CC%EE%BD%E1" class="keyword">竹野潤</a></li><li><a class="keyword" href="https://d.hatena.ne.jp/keyword/%BC%AB%CD%B3%B9%F1%CC%B1%BC%D2">自由国民社</a></li></ul><a href="https://www.amazon.co.jp/dp/B0CMQCMQJN?tag=ledsun-22&amp;linkCode=ogi&amp;th=1&amp;psc=1" class="asin-detail-buy" target="_blank" rel="noopener">Amazon</a></div></div></p> <p><a href="https://bookplus.nikkei.com/atcl/column/011000181/021600009/">&#x7BA1;&#x7406;&#x8077;&#x5FC5;&#x8AAD; &#x9806;&#x756A;&#x306B;&#x8AAD;&#x3080;&#x3068;&#x7406;&#x89E3;&#x304C;&#x6DF1;&#x307E;&#x308B;&#x300C;&#x30DE;&#x30CD;&#x30B8;&#x30E1;&#x30F3;&#x30C8;&#x306E;&#x540D;&#x8457;&#x300D;11&#x518A; | &#x65E5;&#x7D4C;BOOK&#x30D7;&#x30E9;&#x30B9;</a> で紹介されていた本です。 とりあえず一冊目を読んでみました。</p> <p>マネジメントの本は経営者からチームリーダー向けまでターゲットがわかりにくいことがあります。 この本は、部長、課長くらいの日本企業の中間管理職向けの本です。</p> <p>1 on 1の話は3章からはじまります。 1~2章をつかってマネージャーの仕事を説明しています。 100ページくらいにコンパクトにまとまっていてわかりやすかったです。</p> <p>1 on 1 の具体的なハウツーは3章に書いてあります。 僕の所属組織は筆者の方の所属組織とは形が違うのでハウツーをそのままでは使いにくいです。</p> <p>4章以降も1 on 1と絡めてはいますが、マネージャーの仕事全般の話で興味深かったです。</p> <p>印象深かった内容</p> <ul> <li>部下に、今の仕事の延長線上に、将来なりたい自分がいることをイメージさせる</li> <li>部下の言う不満を代わりに解決するのではなく、解決のためにできることのア<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%C7%A5%A2">イデア</a>出しを手伝う</li> <li>部下に(目標管理上の)期待する行動や成果を伝える</li> <li>1 on 1 で、マネージャーに「改めて欲しいこと」「直して欲しいこと」「マネージャーとして不足していると感じるところ」を聞く</li> <li>毎月一回、自分のオススメ本を3分で紹介するイベント</li> <li>部下の成長は「仕事から得られる実体験」が7割</li> <li>任せた仕事を投げ出したり、妥協しようとしたら、とがめる</li> <li>数値目標は再現がないのでミッションをつくった</li> <li>能力の低い部下の方が、一度に伸ばせる能力の幅は小さい</li> <li>ベテランを外回りの営業からチーム内全員の事務作業担当にコンバートした</li> <li>Z世代では「出世に興味ない」というが、「仕事のできるかっこいい<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D3%A5%B8%A5%CD%A5%B9%A5%D1%A1%BC%A5%BD%A5%F3">ビジネスパーソン</a>になりたい」と思ってたりする</li> <li>スキルを身につけるには回数が大事。一回で上手くいかなくても、我慢して見守る</li> <li>若者は、大量の情報を得られる。情報を取捨選択する社会経験が足りていない。混乱して01で判断しがち。解像度高く理解できるようにサポートが必要</li> <li>1 on 1 で初心を思い出させる</li> <li>本当に困った部下を育てる方法が載った本はない</li> </ul> <p>この中でも特に自分に足りていない点は、次の2点かなと思います。</p> <ul> <li>組織の目指す方向をくわしく説明する</li> <li>各メンバーの今の仕事と組織の方向が一致している事を説明する</li> </ul> <p>本当に、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B5%A1%BC%A5%D0%A5%F3%A5%C8%A5%EA%A1%BC%A5%C0%A1%BC%A5%B7%A5%C3%A5%D7">サーバントリーダーシップ</a>というか、部活のマネージャー的なマネージャーなんだなと、思いました。 新人マネージャーが一冊目に読む本として、内容の網羅具合もページのコンパクトさも良さそうです。</p> ledsun 開発チームの人数とふるまいイメージ hatenablog://entry/6801883189084189772 2024-02-18T16:49:03+09:00 2024-02-18T16:49:03+09:00 チームの自律性 お仕事でソフトウェアを開発するプログラマーチームに対して、僕が持っているイメージを整理しました。 人数 2 リードとサブ コンビ 3 リードとサブ x 2 コンビとサポート チーム 4 リードとサブ x 3 チームとサポート チーム 5 マネージャーとメンバー x 4 チームとサポート コミュニティ この表は、右に行くほどチームが自律性高くふるまうことをあらわします。 自律性が高い方が良いとは限りません。 自律性が低いと5人以上のチームでは、チームマネジメントを専任する人が必要になると思います。 それをマネージャーとメンバーと表現しています。 プログラマーの中にはチームマネジメ… <h2 id="チームの自律性">チームの自律性</h2> <p>お仕事でソフトウェアを開発する<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D7%A5%ED%A5%B0%A5%E9%A5%DE%A1%BC">プログラマー</a>チームに対して、僕が持っているイメージを整理しました。</p> <table> <thead> <tr> <th>人数</th> <th> </th> <th> </th> <th> </th> </tr> </thead> <tbody> <tr> <td>2</td> <td> リードとサブ </td> <td> コンビ </td> <td> </td> </tr> <tr> <td>3</td> <td> リードとサブ x 2 </td> <td> コンビとサポート </td> <td> チーム </td> </tr> <tr> <td>4</td> <td> リードとサブ x 3</td> <td> チームとサポート </td> <td> チーム </td> </tr> <tr> <td>5</td> <td> マネージャーとメンバー x 4 </td> <td> チームとサポート </td> <td> コミュニティ </td> </tr> </tbody> </table> <p>この表は、右に行くほどチームが自律性高くふるまうことをあらわします。</p> <p>自律性が高い方が良いとは限りません。 自律性が低いと5人以上のチームでは、チームマネジメントを専任する人が必要になると思います。 それを<strong>マネージャーとメンバー</strong>と表現しています。 <a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D7%A5%ED%A5%B0%A5%E9%A5%DE%A1%BC">プログラマー</a>の中にはチームマネジメントを専任したくない方が一定数います。 もしも、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D7%A5%ED%A5%B0%A5%E9%A5%DE%A1%BC">プログラマー</a>からチームリーダーになり、チームマネジメント専任になりたくない場合、チーム人数が少ないうちから自律性を高めておくのがオススメです。</p> <p>チームの人数が増えてから自律性を高めるには時間が掛かります。 チームメンバー同士が対話する機会を設け練習を繰り返す必要があります。 週1回程度の話し合いの機会を半年~一年くらい続けると、自律性が高まるように思います。</p> <p>チームの自律性を重視するときに注意事項があります。 2人チームが<strong>リードとサブ</strong>になるか<strong>コンビ</strong>になるかは、性格の相性に強く依存します。 2チームでは、自律性をあまり重視しない方が無難です。</p> <p>一方、3~4人では自律性を重視すると良いと思います。 <strong>リードとサブ x n</strong> の状態で人数が増えると、リーダとチームメンバーのコミュニケーションが増え、チームメンバー同士のコミュニケーションが増えません。 リーダーがコミュニケーションにつかうコストが増え、チームメンバー同士のコミュニケーションを増やす施策が打てなくなります。 チームの人数が増えるとチームのパフォーマンスは一度下がります。 チームのパフォーマンスを上げるために、リーダとチームメンバーのコミュニケーションに時間を使いたくなると思います。 一度パフォーマンスが上がってからチームの自律性を高めようとしても、チームの自律性を高めるには再びパフォーマンスを一時的に下げる必要があります。 怖くてできません。 「パフォーマンスが上がっているし、自分が我慢すれば良い」と思いがちです。 人間は「自分が我慢すればよい」誘惑に抵抗するのに、ものすごいパワーが要ります。</p> <h2 id="コミュニティ">コミュニティ</h2> <p>上の表をみると、5人チームに<strong>コミュニティ</strong>という謎の状態があります。 <a href="https://kuranuki.sonicgarden.jp/archives/29980">&#x30C1;&#x30FC;&#x30E0;&#x3068;&#x30B3;&#x30DF;&#x30E5;&#x30CB;&#x30C6;&#x30A3;&#x306E;&#x9055;&#x3044;&#x3001;&#x4F1A;&#x793E;&#x30FB;&#x7D44;&#x7E54;&#x3092;&#x3069;&#x3046;&#x6349;&#x3048;&#x308B;&#x304B; | Social Change!</a> に出てきているコミュニティをイメージしています。 なぜ、チームではなくコミュニティなのかというと、5人以上になると、ミッションに対して100%動いていないメンバーがでてきます。 4人のチームはめちゃくちゃ上手く行っていると4人分の成果がでます。 5人のチームはめちゃくちゃ上手く行っても4.5人分の成果です。</p> <p>スキルの差からではなく、タスクの投入量が上手くコン<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C8%A5%ED%A1%BC%A5%EB">トロール</a>できなくなるために起きます。 手の空くメンバーは特定の人物ではありません。時々によって手が空くメンバーは変わります。 タスクを上手く用意すれば5人分の成果がでるようになるかというと、僕の経験則では、なりません。</p> <p><strong>チームにミッション以外のことに取り組む余裕が生まれた状態</strong>と捉えるとよいです。 ミッションとは別の<strong>緊急ではないが重要なタスク</strong>を用意しておいて、チームメンバーに取り組ませるとよいでしょう。 これはチームの余裕です。 4人までのチームでは<strong>緊急ではないが重要なタスク</strong>は正式なタスクにしたり、リーダーが空き時間でこっそりやったりという工夫が必要です。 5人以上のチームがコミュニティになると、チームメンバーが自主的に<strong>緊急ではないが重要なタスク</strong>に取り組めるようになります。</p> <p>また、もともと4.5人分の成果しか出ないので、メンバーの離脱や加入が容易になります。 一人離脱しても12.5パーセントしか下がらないので<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%D1%A5%AF">インパク</a>トが弱いです。 一人加入にしても1人分の成果は期待されないので、新メンバーの負担が低いです。 このメンバーの出入りが容易な状態が、チームではなく場であり<strong>コミュニティ</strong>です。</p> <h2 id="チームの自律性を高めるコツ">チームの自律性を高めるコツ</h2> <p>チームの自律性を高めるコツは、チームメンバーに縄張りを作らせないことです。 人間は縄張り意識を持つと、その作業を他人にされるのが苦痛に感じます。 また、縄張り外の仕事するのがおっくうになります。</p> <p>縄張りをつくらせないためには、リーダーはチームメンバーの扱いを変えてはいけません。 チームメンバーのスキル、年齢、性別、得意なことが違っても扱いを変えません。 経歴20年のベテランと新卒半年の新人の扱いを同じくします。 スキルが足りない部分のサポートは必要です。 スキルが足りている作業は平等に割り振ります。</p> <p>チームメンバーによって実行速度の遅い速いの差はありますが、<strong><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%EA%A5%C6%A5%A3%A5%AB%A5%EB%A5%D1%A5%B9">クリティカルパス</a>に乗っていない作業は早さを気にする必要はありません</strong>。 また<strong>時間が掛かっても成果を出せた</strong>体験は、個人の成長に繋がります。 さらに<strong><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%EA%A5%C6%A5%A3%A5%AB%A5%EB%A5%D1%A5%B9">クリティカルパス</a>に乗っていない作業は早さを気にする必要が無い</strong>は頭で理解しても、なかなかその通りにふるまえません。 何度も実際に体験する必要があります。</p> <p>平等に割り振らずに、ベテランにばかり難しい作業を振ると「この領域はおれの仕事」と縄張りを作りはじめます。 ベテランほど陥りやすいので、意図的にコン<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C8%A5%ED%A1%BC%A5%EB">トロール</a>する必要があります。 コン<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C8%A5%ED%A1%BC%A5%EB">トロール</a>せずに放置しておくと、よほどの人格者でないと<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D6%A5%EA%A5%EA%A5%A2%A5%F3">ブリリアン</a>トジャーク化します。 すでに<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D6%A5%EA%A5%EA%A5%A2%A5%F3">ブリリアン</a>トジャーク化していてコン<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C8%A5%ED%A1%BC%A5%EB">トロール</a>できず、 短期的な成果よりチームの成長が重要な場面では、チームから外れてもらうことを考えてもいいかもしれません。</p> <p>継続して開発していると、前回機能Aを担当した人に今回も機能Aをお願いしたくなります。 よほど急ぎでない限り担当はグルグル変えるようにしましょう。 繰り返しですが、<strong><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%EA%A5%C6%A5%A3%A5%AB%A5%EB%A5%D1%A5%B9">クリティカルパス</a>に乗っていない作業は早さを気にする必要はありません</strong>。 プロジェクトの進行が早くならないのに、縄張り作りを強化するのは、なにも得しませんよね。</p> ledsun MySQLのロックに関する調査メモ その4 hatenablog://entry/6801883189083843717 2024-02-17T10:56:56+09:00 2024-02-17T10:56:56+09:00 MySQLのロックに関する調査メモ その3 - @ledsun blog の続きです。 SHOW ENGINE INNODB STATUS\Gで出力されるロック情報を眺めていたら意味がわかってきました。 まずテーブルに格納されているレコードの情報です。 select * from mysqlcasual; +----+------+------+ | id | col1 | col2 | +----+------+------+ | 1 | 2 | 0 | | 2 | 4 | 0 | | 3 | 6 | 0 | | 4 | 8 | 0 | | 5 | 10 | 0 | | 6 | 12 | 0… <p><a href="https://ledsun.hatenablog.com/entry/2024/02/17/091828">MySQL&#x306E;&#x30ED;&#x30C3;&#x30AF;&#x306B;&#x95A2;&#x3059;&#x308B;&#x8ABF;&#x67FB;&#x30E1;&#x30E2; &#x305D;&#x306E;3 - @ledsun blog</a> の続きです。</p> <p><code>SHOW ENGINE INNODB STATUS\G</code>で出力されるロック情報を眺めていたら意味がわかってきました。</p> <p>まずテーブルに格納されているレコードの情報です。</p> <pre class="code lang-sql" data-lang="sql" data-unlink> <span class="synStatement">select</span> * <span class="synSpecial">from</span> mysqlcasual; +<span class="synComment">----+------+------+</span> | id | col1 | col2 | +<span class="synComment">----+------+------+</span> | <span class="synConstant">1</span> | <span class="synConstant">2</span> | <span class="synConstant">0</span> | | <span class="synConstant">2</span> | <span class="synConstant">4</span> | <span class="synConstant">0</span> | | <span class="synConstant">3</span> | <span class="synConstant">6</span> | <span class="synConstant">0</span> | | <span class="synConstant">4</span> | <span class="synConstant">8</span> | <span class="synConstant">0</span> | | <span class="synConstant">5</span> | <span class="synConstant">10</span> | <span class="synConstant">0</span> | | <span class="synConstant">6</span> | <span class="synConstant">12</span> | <span class="synConstant">0</span> | | <span class="synConstant">7</span> | <span class="synConstant">14</span> | <span class="synConstant">0</span> | | <span class="synConstant">8</span> | <span class="synConstant">16</span> | <span class="synConstant">0</span> | +<span class="synComment">----+------+------+</span> </pre> <h2 id="1つ目のレコードを選択">1つ目のレコードを選択</h2> <pre class="code lang-sql" data-lang="sql" data-unlink><span class="synSpecial">begin</span>;SELECT id, col1 <span class="synSpecial">FROM</span> mysqlcasual <span class="synSpecial">WHERE</span> col1 = <span class="synConstant">2</span> <span class="synSpecial">FOR</span> UPDATE; </pre> <p>三つのロック情報が出てきます。 一つ目から見ていきます。</p> <pre class="code" data-lang="" data-unlink>RECORD LOCKS space id 173 page no 4 n bits 80 index idx_col1 of table `sandbox`.`mysqlcasual` trx id 7731 lock_mode X Record lock, heap no 2 PHYSICAL RECORD: n_fields 2; compact format; info bits 0 0: len 4; hex 80000002; asc ;; 1: len 4; hex 80000001; asc ;;</pre> <p><code>index idx_col1 of table</code> はインデックス idx_col1 に対するロックであることを表しています。 <code>lock_mode X</code>は<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%CD%A5%AF%A5%B9">ネクス</a>ト<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AD%A1%BC%A5%ED%A5%C3%A5%AF">キーロック</a>であることを表しています。無印が<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%CD%A5%AF%A5%B9">ネクス</a>ト<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AD%A1%BC%A5%ED%A5%C3%A5%AF">キーロック</a>です。</p> <pre class="code" data-lang="" data-unlink> 0: len 4; hex 80000002; asc ;; 1: len 4; hex 80000001; asc ;;</pre> <p>はロックしているレコードを表してます。 <code>80000002</code>はcol1カラムの値が2、<code>80000001</code>はidカラムの値です。</p> <p>選択するレコードを変えてみます。</p> <h2 id="2つ目のレコードを選択">2つ目のレコードを選択</h2> <pre class="code lang-sql" data-lang="sql" data-unlink><span class="synSpecial">begin</span>;SELECT id, col1 <span class="synSpecial">FROM</span> mysqlcasual <span class="synSpecial">WHERE</span> col1 = <span class="synConstant">4</span> <span class="synSpecial">FOR</span> UPDATE; </pre> <pre class="code" data-lang="" data-unlink>RECORD LOCKS space id 173 page no 4 n bits 80 index idx_col1 of table `sandbox`.`mysqlcasual` trx id 7733 lock_mode X Record lock, heap no 3 PHYSICAL RECORD: n_fields 2; compact format; info bits 0 0: len 4; hex 80000004; asc ;; 1: len 4; hex 80000002; asc ;;</pre> <p><code>80000004</code>と<code>80000002</code>になりました。</p> <p>次のロック情報を見てみましょう。</p> <h2 id="2つ目のロック情報">2つ目のロック情報</h2> <pre class="code" data-lang="" data-unlink>RECORD LOCKS space id 173 page no 3 n bits 80 index PRIMARY of table `sandbox`.`mysqlcasual` trx id 7733 lock_mode X locks rec but not gap Record lock, heap no 3 PHYSICAL RECORD: n_fields 5; compact format; info bits 0 0: len 4; hex 80000002; asc ;; 1: len 6; hex 00000000162a; asc *;; 2: len 7; hex aa0000011e011d; asc ;; 3: len 4; hex 80000004; asc ;; 4: len 4; hex 80000000; asc ;;</pre> <p><code>index PRIMARY of table</code>とあるので、プライマリーインデックスへのロックです。<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%BB%A5%AB%A5%F3%A5%C0%A5%EA">セカンダリ</a>ーインデックスをロックするとプライマリーインデックスもロックされるやつですね。 <code>lock_mode X locks rec but not gap</code>は、レコードロックを表してます。ギャップロックでないものがレコードロックです。</p> <pre class="code" data-lang="" data-unlink> 0: len 4; hex 80000002; asc ;; 1: len 6; hex 00000000162a; asc *;; 2: len 7; hex aa0000011e011d; asc ;; 3: len 4; hex 80000004; asc ;; 4: len 4; hex 80000000; asc ;;</pre> <p><code>80000002</code>と<code>80000004</code>と<code>80000000</code>はレコードの値を表していそうです。 <code>00000000162a</code>と<code>aa0000011e011d</code>は謎です。プライマリーインデックスと<a class="keyword" href="https://d.hatena.ne.jp/keyword/idex">idex</a>_col1を表しているのかもしれません。 上は2つ目のレコードです。1つ目のレコードは下になります。</p> <pre class="code" data-lang="" data-unlink> 0: len 4; hex 80000001; asc ;; 1: len 6; hex 00000000162a; asc *;; 2: len 7; hex aa0000011e0110; asc ;; 3: len 4; hex 80000002; asc ;; 4: len 4; hex 80000000; asc ;;</pre> <p><code>00000000162a</code>は変わりません。 <code>aa0000011e011d</code>は<code>aa0000011e0110</code>になりました。 うーん、これはちょっとよくわかりません。</p> <h2 id="3つ目のロック情報">3つ目のロック情報</h2> <pre class="code" data-lang="" data-unlink>RECORD LOCKS space id 173 page no 4 n bits 80 index idx_col1 of table `sandbox`.`mysqlcasual` trx id 7733 lock_mode X locks gap before rec Record lock, heap no 4 PHYSICAL RECORD: n_fields 2; compact format; info bits 0 0: len 4; hex 80000006; asc ;; 1: len 4; hex 80000003; asc ;;</pre> <p><code>index idx_col1 of table</code>なのでidx_col1へのロックです。 <code>lock_mode X locks gap before rec</code>なので、ギャップロックです。</p> <pre class="code" data-lang="" data-unlink> 0: len 4; hex 80000006; asc ;; 1: len 4; hex 80000003; asc ;;</pre> <p>は、3つ目のレコードを表しています。 ギャップロックなので、3つ目のレコードの手前までをロックしているようです。</p> <p>もしかして、1つ目の<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%CD%A5%AF%A5%B9">ネクス</a>ト<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AD%A1%BC%A5%ED%A5%C3%A5%AF">キーロック</a>を、2つ目のレコードロックと3つ目のギャップロックで実現していることを表現しているのでしょうか?</p> ledsun MySQLのロックに関する調査メモ その3 hatenablog://entry/6801883189083822231 2024-02-17T09:18:28+09:00 2024-02-17T09:18:28+09:00 MySQLのロックに関する調査メモ その2 - @ledsun blog の続きです。 概念だけ勉強しても理解に限度があります。 実際にロックを起こして観察してみました。 InnoDBのロックの範囲とネクストキーロックの話 - かみぽわーる を参考にしました。 MySQL Shellを起動しMySQLに接続します。 \connect --mysql --user root \use sandbox \sql sandboxスキーマは練習用に作ってあったものを使い回しています。 テーブルを作成します。 CREATE TABLE `mysqlcasual` ( `id` int(11) NOT N… <p><a href="https://ledsun.hatenablog.com/entry/2024/02/07/184746">MySQL&#x306E;&#x30ED;&#x30C3;&#x30AF;&#x306B;&#x95A2;&#x3059;&#x308B;&#x8ABF;&#x67FB;&#x30E1;&#x30E2; &#x305D;&#x306E;2 - @ledsun blog</a> の続きです。 概念だけ勉強しても理解に限度があります。 実際にロックを起こして観察してみました。</p> <p><a href="https://blog.kamipo.net/entry/2013/12/03/235900">InnoDB&#x306E;&#x30ED;&#x30C3;&#x30AF;&#x306E;&#x7BC4;&#x56F2;&#x3068;&#x30CD;&#x30AF;&#x30B9;&#x30C8;&#x30AD;&#x30FC;&#x30ED;&#x30C3;&#x30AF;&#x306E;&#x8A71; - &#x304B;&#x307F;&#x307D;&#x308F;&#x30FC;&#x308B;</a> を参考にしました。 <a class="keyword" href="https://d.hatena.ne.jp/keyword/MySQL">MySQL</a> Shellを起動し<a class="keyword" href="https://d.hatena.ne.jp/keyword/MySQL">MySQL</a>に接続します。</p> <pre class="code" data-lang="" data-unlink>\connect --mysql --user root \use sandbox \sql</pre> <p>sandbox<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AD%A1%BC%A5%DE">スキーマ</a>は練習用に作ってあったものを使い回しています。 テーブルを作成します。</p> <pre class="code lang-sql" data-lang="sql" data-unlink><span class="synStatement">CREATE</span> <span class="synSpecial">TABLE</span> `mysqlcasual` ( `id` <span class="synType">int</span>(<span class="synConstant">11</span>) <span class="synStatement">NOT</span> <span class="synSpecial">NULL</span> AUTO_INCREMENT, `col1` <span class="synType">int</span>(<span class="synConstant">11</span>) <span class="synStatement">NOT</span> <span class="synSpecial">NULL</span>, `col2` <span class="synType">int</span>(<span class="synConstant">11</span>) <span class="synStatement">NOT</span> <span class="synSpecial">NULL</span> <span class="synSpecial">DEFAULT</span> <span class="synSpecial">'</span><span class="synConstant">0</span><span class="synSpecial">'</span>, PRIMARY KEY (`id`), KEY `idx_col1` (`col1`) ) ENGINE=InnoDB <span class="synSpecial">DEFAULT</span> CHARSET=utf8; <span class="synStatement">INSERT</span> <span class="synSpecial">INTO</span> `mysqlcasual`(`col1`) <span class="synSpecial">VALUES</span> (<span class="synConstant">2</span>),(<span class="synConstant">4</span>),(<span class="synConstant">6</span>),(<span class="synConstant">8</span>),(<span class="synConstant">10</span>),(<span class="synConstant">12</span>),(<span class="synConstant">14</span>),(<span class="synConstant">16</span>); </pre> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/MySQL">MySQL</a> 5.7 以降では、ここからの手順が変わっています<a href="#f-63e7f36c" id="fn-63e7f36c" name="fn-63e7f36c" title="https://dev.mysql.com/doc/refman/8.0/ja/innodb-enabling-monitors.html">*1</a>。</p> <pre class="code lang-sql" data-lang="sql" data-unlink><span class="synStatement">CREATE</span> <span class="synSpecial">TABLE</span> innodb_lock_monitor(a <span class="synType">int</span>) ENGINE=InnoDB; </pre> <p>の代わりに</p> <pre class="code lang-sql" data-lang="sql" data-unlink><span class="synStatement">SET</span> GLOBAL innodb_status_output=<span class="synSpecial">ON</span>; <span class="synStatement">SET</span> GLOBAL innodb_status_output_locks=<span class="synSpecial">ON</span>; </pre> <p>します。 また、共有ロックではロックが表示されませんでした。</p> <pre class="code lang-sql" data-lang="sql" data-unlink><span class="synSpecial">BEGIN</span>; <span class="synStatement">SELECT</span> id, col1 <span class="synSpecial">FROM</span> mysqlcasual <span class="synSpecial">WHERE</span> col1 = <span class="synConstant">4</span> <span class="synStatement">LOCK</span> <span class="synStatement">IN</span> <span class="synSpecial">SHARE</span> <span class="synSpecial">MODE</span>; </pre> <p>の代わりに</p> <pre class="code lang-sql" data-lang="sql" data-unlink><span class="synSpecial">BEGIN</span>; <span class="synStatement">SELECT</span> id, col1 <span class="synSpecial">FROM</span> mysqlcasual <span class="synSpecial">WHERE</span> col1 = <span class="synConstant">4</span> <span class="synSpecial">FOR</span> <span class="synStatement">UPDATE</span>; </pre> <p>を試しました。</p> <pre class="code lang-sql" data-lang="sql" data-unlink>SHOW ENGINE INNODB STATUS\G </pre> <p>を実行するとTRANSACTIONSセクションにロック情報が表示されました。</p> <pre class="code" data-lang="" data-unlink>RECORD LOCKS space id 173 page no 4 n bits 80 index idx_col1 of table `sandbox`.`mysqlcasual` trx id 7717 lock_mode X Record lock, heap no 3 PHYSICAL RECORD: n_fields 2; compact format; info bits 0 0: len 4; hex 80000004; asc ;; 1: len 4; hex 80000002; asc ;;</pre> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%CD%A5%AF%A5%B9">ネクス</a>ト<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AD%A1%BC%A5%ED%A5%C3%A5%AF">キーロック</a>?</p> <pre class="code" data-lang="" data-unlink>RECORD LOCKS space id 173 page no 3 n bits 80 index PRIMARY of table `sandbox`.`mysqlcasual` trx id 7717 lock_mode X locks rec but not gap Record lock, heap no 3 PHYSICAL RECORD: n_fields 5; compact format; info bits 0 0: len 4; hex 80000002; asc ;; 1: len 6; hex 00000000162a; asc *;; 2: len 7; hex aa0000011e011d; asc ;; 3: len 4; hex 80000004; asc ;; 4: len 4; hex 80000000; asc ;;</pre> <p>インデックスレコードロック?</p> <pre class="code" data-lang="" data-unlink>RECORD LOCKS space id 173 page no 4 n bits 80 index idx_col1 of table `sandbox`.`mysqlcasual` trx id 7717 lock_mode X locks gap before rec Record lock, heap no 4 PHYSICAL RECORD: n_fields 2; compact format; info bits 0 0: len 4; hex 80000006; asc ;; 1: len 4; hex 80000003; asc ;;</pre> <p>ギャップロック?</p> <p>出力された情報の読み方が謎です。</p> <div class="footnote"> <p class="footnote"><a href="#fn-63e7f36c" id="f-63e7f36c" name="f-63e7f36c" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a href="https://dev.mysql.com/doc/refman/8.0/ja/innodb-enabling-monitors.html">https://dev.mysql.com/doc/refman/8.0/ja/innodb-enabling-monitors.html</a></span></p> </div> ledsun iroshizuku<色彩雫> 冬柿 hatenablog://entry/6801883189081590696 2024-02-08T18:13:07+09:00 2024-02-08T18:13:07+09:00 万年筆のインクです。 PILOT 万年筆水性インキ 色彩雫/iroshizuku ミニボトル 15ml【冬柿】 INK15FGパイロットAmazon 赤っぽいのかと思っていました。 茶色とオレンジの間ぐらいの色でした。 好きな感じの色です。 冬柿で書いた文字の写真 写真で見ると、赤ですね。 このメモは別の資料を見ながら書いたメモです。 メモだけ見ても意味が通じないと思います。 <p>万年筆のインクです。</p> <p><div class="hatena-asin-detail"><a href="https://www.amazon.co.jp/dp/B01J3UM6ZE?tag=ledsun-22&amp;linkCode=osi&amp;th=1&amp;psc=1" class="hatena-asin-detail-image-link" target="_blank" rel="noopener"><img src="https://m.media-amazon.com/images/I/41LiomeiefL._SL500_.jpg" class="hatena-asin-detail-image" alt="PILOT 万年筆水性インキ 色彩雫/iroshizuku ミニボトル 15ml【冬柿】 INK15FG" title="PILOT 万年筆水性インキ 色彩雫/iroshizuku ミニボトル 15ml【冬柿】 INK15FG"></a><div class="hatena-asin-detail-info"><p class="hatena-asin-detail-title"><a href="https://www.amazon.co.jp/dp/B01J3UM6ZE?tag=ledsun-22&amp;linkCode=osi&amp;th=1&amp;psc=1" target="_blank" rel="noopener">PILOT 万年筆水性インキ 色彩雫/iroshizuku ミニボトル 15ml【冬柿】 INK15FG</a></p><ul class="hatena-asin-detail-meta"><li><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D1%A5%A4%A5%ED">パイロ</a>ット</li></ul><a href="https://www.amazon.co.jp/dp/B01J3UM6ZE?tag=ledsun-22&amp;linkCode=osi&amp;th=1&amp;psc=1" class="asin-detail-buy" target="_blank" rel="noopener">Amazon</a></div></div></p> <p>赤っぽいのかと思っていました。 茶色とオレンジの間ぐらいの色でした。 好きな感じの色です。</p> <p><figure class="figure-image figure-image-fotolife" title="冬柿で書いた文字の写真"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/l/ledsun/20240208/20240208141743.jpg" width="1200" height="789" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>冬柿で書いた文字の写真</figcaption></figure></p> <p>写真で見ると、赤ですね。</p> <p>このメモは別の資料を見ながら書いたメモです。 メモだけ見ても意味が通じないと思います。</p> ledsun MySQLのロックに関する調査メモ その2 hatenablog://entry/6801883189081324712 2024-02-07T18:47:46+09:00 2024-02-07T18:47:46+09:00 MySQLのロックに関する調査メモ その1 - @ledsun blog の続きです。 MySQLのロックについて - SH2の日記 のPDFを読みました。 面白かったです。 MySQL(というかInnoDB)の最初のスタート地点が「絶対にファントムリードさせたくない」なようです。 つまり一度トランザクションをはじめたら、他のトランザクションでの操作を見たくありません。 例えば、自トランザクションで見ている行と行の間に、他トランザクションからInsertされたくありません。 ということは行単位のロックでは足りなくて、自トランザクションで見ている範囲をロックしたいです。 これが、ギャップロックと… <p><a href="https://ledsun.hatenablog.com/entry/2024/02/06/180805">MySQL&#x306E;&#x30ED;&#x30C3;&#x30AF;&#x306B;&#x95A2;&#x3059;&#x308B;&#x8ABF;&#x67FB;&#x30E1;&#x30E2; &#x305D;&#x306E;1 - @ledsun blog</a> の続きです。</p> <p><a href="https://sh2.hatenablog.jp/entry/20140914">MySQL&#x306E;&#x30ED;&#x30C3;&#x30AF;&#x306B;&#x3064;&#x3044;&#x3066; - SH2&#x306E;&#x65E5;&#x8A18;</a> のPDFを読みました。 面白かったです。</p> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/MySQL">MySQL</a>(というか<a class="keyword" href="https://d.hatena.ne.jp/keyword/InnoDB">InnoDB</a>)の最初のスタート地点が「絶対にファントムリードさせたくない」なようです。 つまり一度<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C8%A5%E9%A5%F3%A5%B6%A5%AF%A5%B7%A5%E7%A5%F3">トランザクション</a>をはじめたら、他の<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C8%A5%E9%A5%F3%A5%B6%A5%AF%A5%B7%A5%E7%A5%F3">トランザクション</a>での操作を見たくありません。 例えば、自<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C8%A5%E9%A5%F3%A5%B6%A5%AF%A5%B7%A5%E7%A5%F3">トランザクション</a>で見ている行と行の間に、他<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C8%A5%E9%A5%F3%A5%B6%A5%AF%A5%B7%A5%E7%A5%F3">トランザクション</a>からInsertされたくありません。 ということは行単位のロックでは足りなくて、自<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C8%A5%E9%A5%F3%A5%B6%A5%AF%A5%B7%A5%E7%A5%F3">トランザクション</a>で見ている範囲をロックしたいです。 これが、ギャップロックと<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%CD%A5%AF%A5%B9">ネクス</a>ト<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AD%A1%BC%A5%ED%A5%C3%A5%AF">キーロック</a>のようです。</p> <p>今の段階ではギャップロックと<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%CD%A5%AF%A5%B9">ネクス</a>ト<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AD%A1%BC%A5%ED%A5%C3%A5%AF">キーロック</a>の違いはわかっていません。 特に、なぜ<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%CD%A5%AF%A5%B9">ネクス</a>ト<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AD%A1%BC%A5%ED%A5%C3%A5%AF">キーロック</a>をしなければいけないのかよくわかりません。</p> <p>どうもこの辺の思想は「<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C8%A5%E9%A5%F3%A5%B6%A5%AF%A5%B7%A5%E7%A5%F3">トランザクション</a>処理 概念と技法」のでしょうか?</p> <p><div class="hatena-asin-detail"><a href="https://www.amazon.co.jp/dp/4822281027?tag=ledsun-22&amp;linkCode=osi&amp;th=1&amp;psc=1" class="hatena-asin-detail-image-link" target="_blank" rel="noopener"><img src="https://m.media-amazon.com/images/I/41E27YWD72L._SL500_.jpg" class="hatena-asin-detail-image" alt="トランザクション処理 上" title="トランザクション処理 上"></a><div class="hatena-asin-detail-info"><p class="hatena-asin-detail-title"><a href="https://www.amazon.co.jp/dp/4822281027?tag=ledsun-22&amp;linkCode=osi&amp;th=1&amp;psc=1" target="_blank" rel="noopener">トランザクション処理 上</a></p><ul class="hatena-asin-detail-meta"><li><span class="hatena-asin-detail-label">作者:</span><a href="https://d.hatena.ne.jp/keyword/%A5%B8%A5%E0%20%A5%B0%A5%EC%A5%A4" class="keyword">ジム グレイ</a>,<a href="https://d.hatena.ne.jp/keyword/%A5%A2%A5%F3%A5%C9%A5%EC%A5%A2%A5%B9%20%A5%ED%A5%A4%A5%BF%A1%BC" class="keyword">アンドレアス ロイター</a></li><li><a class="keyword" href="https://d.hatena.ne.jp/keyword/%C6%FC%B7%D0BP">日経BP</a></li></ul><a href="https://www.amazon.co.jp/dp/4822281027?tag=ledsun-22&amp;linkCode=osi&amp;th=1&amp;psc=1" class="asin-detail-buy" target="_blank" rel="noopener">Amazon</a></div></div></p> <p>もう一つ、<a class="keyword" href="https://d.hatena.ne.jp/keyword/MySQL">MySQL</a>の面白い特徴は、プライマリーインデックスのリーフに行があることです。</p> <p><a href="https://dev.mysql.com/doc/refman/8.0/ja/innodb-index-types.html">MySQL :: MySQL 8.0 &#x30EA;&#x30D5;&#x30A1;&#x30EC;&#x30F3;&#x30B9;&#x30DE;&#x30CB;&#x30E5;&#x30A2;&#x30EB; :: 15.6.2.1 &#x30AF;&#x30E9;&#x30B9;&#x30BF;&#x30A4;&#x30F3;&#x30C7;&#x30C3;&#x30AF;&#x30B9;&#x3068;&#x30BB;&#x30AB;&#x30F3;&#x30C0;&#x30EA;&#x30A4;&#x30F3;&#x30C7;&#x30C3;&#x30AF;&#x30B9;</a></p> <blockquote><p>すべての <a class="keyword" href="https://d.hatena.ne.jp/keyword/InnoDB">InnoDB</a> テーブルは、行のデータが格納されている<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%B9%A5%BF">クラスタ</a>化されたインデックスと呼ばれる特別なインデックスを持っています。</p></blockquote> <p><a href="https://dev.mysql.com/doc/refman/8.0/ja/innodb-locking.html">MySQL :: MySQL 8.0 &#x30EA;&#x30D5;&#x30A1;&#x30EC;&#x30F3;&#x30B9;&#x30DE;&#x30CB;&#x30E5;&#x30A2;&#x30EB; :: 15.7.1 InnoDB &#x30ED;&#x30C3;&#x30AF;</a> に次のように書いてあります。</p> <blockquote><p>レコードロックは、インデックスレコードのロックです。</p></blockquote> <p>どうもこういうことを説明しているみたいです。</p> <p>また、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%BB%A5%AB%A5%F3%A5%C0%A5%EA">セカンダリ</a>ーインデックスをロックするときは、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%BB%A5%AB%A5%F3%A5%C0%A5%EA">セカンダリ</a>ーインデックスとプライマリーインデックスのインデックスレコードを両方ロックするそうです。</p> ledsun MySQLのロックに関する調査メモ その1 hatenablog://entry/6801883189081066887 2024-02-06T18:08:05+09:00 2024-02-06T18:08:05+09:00 MySQLのロックのついては MySQL :: MySQL 8.0 リファレンスマニュアル :: 15.7.1 InnoDB ロック に書いてあるはずです。 読んでもいまいちよくわかりません。 MySQL/MariaDBとTransactdのInnoDBロック制御詳細 その1 - BizStationブログを合わせて読むとわかりやすいです。 どうやら公式ドキュメントは内部実装の立場で説明しているようです。 たとえば、後者には次のような記述があります。 2つのトランザクションが同じレコードのロックを取得しようとしたときのロックの可否を表したのがInnoDBソースコードの以下の部分です。 このソー… <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/MySQL">MySQL</a>のロックのついては <a href="https://dev.mysql.com/doc/refman/8.0/ja/innodb-locking.html">MySQL :: MySQL 8.0 &#x30EA;&#x30D5;&#x30A1;&#x30EC;&#x30F3;&#x30B9;&#x30DE;&#x30CB;&#x30E5;&#x30A2;&#x30EB; :: 15.7.1 InnoDB &#x30ED;&#x30C3;&#x30AF;</a> に書いてあるはずです。 読んでもいまいちよくわかりません。</p> <p><a href="https://bizstation.hatenablog.com/entry/2014/12/24/103641">MySQL/MariaDB&#x3068;Transactd&#x306E;InnoDB&#x30ED;&#x30C3;&#x30AF;&#x5236;&#x5FA1;&#x8A73;&#x7D30; &#x305D;&#x306E;1 - BizStation&#x30D6;&#x30ED;&#x30B0;</a>を合わせて読むとわかりやすいです。 どうやら公式ドキュメントは内部実装の立場で説明しているようです。 たとえば、後者には次のような記述があります。</p> <blockquote><p>2つの<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C8%A5%E9%A5%F3%A5%B6%A5%AF%A5%B7%A5%E7%A5%F3">トランザクション</a>が同じレコードのロックを取得しようとしたときのロックの可否を表したのが<a class="keyword" href="https://d.hatena.ne.jp/keyword/InnoDB">InnoDB</a><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%BD%A1%BC%A5%B9%A5%B3%A1%BC%A5%C9">ソースコード</a>の以下の部分です。</p></blockquote> <p>このソースコーコメントを<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%DE%A5%C8%A5%EA%A5%C3%A5%AF%A5%B9">マトリックス</a>にしたものが公式ドキュメントに載っています。 ですので、公式ドキュメントは実装者視点で読む必要があります。 利用者視点で読むと理解が難しいようです。</p> <p>後者の文章には、実装者視点の解説があるので理解の手引きになります。</p> ledsun Windows Formの同期イベントハンドラーから入れ子になった非同期関数を呼ぶとデッドロックする hatenablog://entry/6801883189079004435 2024-01-30T18:41:38+09:00 2024-01-30T18:41:38+09:00 await と Task.Result によるデッドロックによるとWindows FormでTaskを使ったときにデッドロックするケースがあるようです。 検証してみます。 デッドロックするソースコード 試しに次のようなコードを書いてみました。 namespace AwaitDeadLockEight { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void button1_Click(object sender, EventArgs e) { label1.Text … <p><a href="http://surferonwww.info/BlogEngine/post/2020/09/20/deadlock-caused-by-await-and-task-result.aspx">await &#x3068; Task.Result &#x306B;&#x3088;&#x308B;&#x30C7;&#x30C3;&#x30C9;&#x30ED;&#x30C3;&#x30AF;</a>によると<a class="keyword" href="https://d.hatena.ne.jp/keyword/Windows">Windows</a> FormでTaskを使ったときに<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C7%A5%C3%A5%C9%A5%ED%A5%C3%A5%AF">デッドロック</a>するケースがあるようです。 検証してみます。</p> <h2 id="デッドロックするソースコード"><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C7%A5%C3%A5%C9%A5%ED%A5%C3%A5%AF">デッドロック</a>する<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%BD%A1%BC%A5%B9%A5%B3%A1%BC%A5%C9">ソースコード</a></h2> <p>試しに次のようなコードを書いてみました。</p> <pre class="code lang-cs" data-lang="cs" data-unlink><span class="synType">namespace</span> AwaitDeadLockEight { <span class="synType">public</span> <span class="synStatement">partial</span> <span class="synType">class</span> <span class="synType">Form1 </span><span class="synStatement">:</span> Form { <span class="synType">public</span> Form1() { InitializeComponent(); } <span class="synType">private</span> <span class="synType">void</span> button1_Click(<span class="synType">object</span> sender, EventArgs e) { label1.Text <span class="synStatement">=</span> <span class="synConstant">&quot;&quot;</span>; <span class="synType">string</span> str <span class="synStatement">=</span> TimeCosumingMethod().Result; label1.Text <span class="synStatement">=</span> str; } <span class="synType">private</span> <span class="synStatement">async</span> Task&lt;<span class="synType">string</span>&gt; TimeCosumingMethod() { <span class="synStatement">await</span> Task.Delay(<span class="synConstant">3000</span>); <span class="synStatement">return</span> <span class="synConstant">&quot;TimeCosumingMethod の戻り値&quot;</span>; } } } </pre> <p>ボタンを押すと確かに固まります。 <code>await Task.Delay(3000);</code>を<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B3%A5%E1%A5%F3%A5%C8%A5%A2%A5%A6%A5%C8">コメントアウト</a>すると動きます。</p> <p>なので、非同期関数を呼ぶだけでは問題ないです。 非同期関数のなかで非同期関数を呼ぶと固まります。</p> <p><code>Task.Delay(3000).Wait();</code>でも固まります。 <code>await</code>を使うかどうかは関係ないようです。</p> <h2 id="デッドロックしないパターン"><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C7%A5%C3%A5%C9%A5%ED%A5%C3%A5%AF">デッドロック</a>しないパターン</h2> <p>次のように呼び出す<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%D9%A5%F3%A5%C8%A5%CF%A5%F3%A5%C9%A5%E9">イベントハンドラ</a>ーを非同期関数にすると期待通りに動きます。</p> <pre class="code lang-cs" data-lang="cs" data-unlink><span class="synType">private</span> <span class="synStatement">async</span> <span class="synType">void</span> button1_Click(<span class="synType">object</span> sender, EventArgs e) { label1.Text <span class="synStatement">=</span> <span class="synConstant">&quot;&quot;</span>; <span class="synType">string</span> str <span class="synStatement">=</span> <span class="synStatement">await</span> TimeCosumingMethod(); label1.Text <span class="synStatement">=</span> str; } <span class="synType">private</span> <span class="synStatement">async</span> Task&lt;<span class="synType">string</span>&gt; TimeCosumingMethod() { <span class="synStatement">await</span> Task.Delay(<span class="synConstant">3000</span>); <span class="synStatement">return</span> <span class="synConstant">&quot;TimeCosumingMethod の戻り値&quot;</span>; } </pre> <p>不思議な挙動です。何が起きているのでしょうか?</p> <h2 id="今のところの仮説">今のところの仮説</h2> <p>ここからさきは、今のところの仮説です。 イマイチ上手く説明できていませんが、現在の理解を書いておきます。</p> <ol> <li>.NETには<a href="https://learn.microsoft.com/ja-jp/dotnet/api/system.threading.synchronizationcontext?view=net-8.0">SynchronizationContext</a>というものがあり、非同期関数の処理結果をUIスレッドのコンテキストに戻している</li> <li>コンテキストを戻すとは、UIスレッドのイベントループのタスクキュー(?)にタスクを積んでいる</li> <li>スレッドは自タスクキューを<a class="keyword" href="https://d.hatena.ne.jp/keyword/LIFO">LIFO</a>で処理する</li> <li>「Task.Delayの結果を処理する」タスクAがUIスレッドのタスクキューに積まれる</li> <li>「TimeCosumingMethodの結果を処理する」タスクBがUIスレッドのタスクキューに積まれる</li> <li><a class="keyword" href="https://d.hatena.ne.jp/keyword/LIFO">LIFO</a>でタスクBから処理しようとするが、タスクAが完了していないので、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C7%A5%C3%A5%C9%A5%ED%A5%C3%A5%AF">デッドロック</a>する</li> </ol> <p>でも、この説明だと<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%D9%A5%F3%A5%C8%A5%CF%A5%F3%A5%C9%A5%E9">イベントハンドラ</a>ーを非同期関数にしたときに上手く動く理由が説明できません。 そもそも <code>「Task.Delayの結果を処理する」タスクAがUIスレッドのタスクキューに積まれる</code> は本当でしょうか? ワーかスレッドのキューに積まれるないのでしょうか? 謎です。</p> <h2 id="参考">参考</h2> <ul> <li><a href="https://ufcpp.net/study/csharp/misc_task.html">[&#x96D1;&#x8A18;] &#x30B9;&#x30EC;&#x30C3;&#x30C9; &#x30D7;&#x30FC;&#x30EB;&#x3068;&#x30BF;&#x30B9;&#x30AF; - C# &#x306B;&#x3088;&#x308B;&#x30D7;&#x30ED;&#x30B0;&#x30E9;&#x30DF;&#x30F3;&#x30B0;&#x5165;&#x9580; | ++C++; // &#x672A;&#x78BA;&#x8A8D;&#x98DB;&#x884C; C</a></li> <li><a href="https://ufcpp.net/blog/2016/12/tipscontextfreetask/">&#x5C0F;&#x30CD;&#x30BF; &#x540C;&#x671F;&#x30B3;&#x30F3;&#x30C6;&#x30AD;&#x30B9;&#x30C8;&#x3092;&#x62FE;&#x308F;&#x306A;&#x3044;Task&#x578B; | ++C++; // &#x672A;&#x78BA;&#x8A8D;&#x98DB;&#x884C; C &#x30D6;&#x30ED;&#x30B0;</a></li> <li><a href="https://ufcpp.net/study/csharp/misc_uithread.html">[&#x96D1;&#x8A18;] GUI &#x3068;&#x975E;&#x540C;&#x671F;&#x51E6;&#x7406; - C# &#x306B;&#x3088;&#x308B;&#x30D7;&#x30ED;&#x30B0;&#x30E9;&#x30DF;&#x30F3;&#x30B0;&#x5165;&#x9580; | ++C++; // &#x672A;&#x78BA;&#x8A8D;&#x98DB;&#x884C; C</a></li> <li><a href="https://ufcpp.net/study/csharp/misc_continuation.html">[&#x96D1;&#x8A18;] &#x7D99;&#x7D9A;&#x3068;&#x5148;&#x7269; - C# &#x306B;&#x3088;&#x308B;&#x30D7;&#x30ED;&#x30B0;&#x30E9;&#x30DF;&#x30F3;&#x30B0;&#x5165;&#x9580; | ++C++; // &#x672A;&#x78BA;&#x8A8D;&#x98DB;&#x884C; C</a></li> </ul> ledsun ruby.wasmのJS::Object#newにブロックでコールバック関数を渡す hatenablog://entry/6801883189078454585 2024-01-27T18:49:16+09:00 2024-01-27T18:49:16+09:00 ruby.wasmのKernel#sleepをどう実装したものか? - @ledsun blog の続きです。 次のようにruby.wasmで動くKernel#sleepを実装できます。 <html> <head> <title>Kernel#sleep</title> <script src="https://cdn.jsdelivr.net/npm/@ruby/head-wasm-wasi@2.4.1-2024-01-26-a/dist/browser.script.iife.js"></script> <script type="text/ruby" data-eval="async">… <p><a href="https://ledsun.hatenablog.com/entry/2024/01/09/203625">ruby.wasm&#x306E;Kernel#sleep&#x3092;&#x3069;&#x3046;&#x5B9F;&#x88C5;&#x3057;&#x305F;&#x3082;&#x306E;&#x304B;&#xFF1F; - @ledsun blog</a> の続きです。</p> <p>次のように<a class="keyword" href="https://d.hatena.ne.jp/keyword/ruby">ruby</a>.wasmで動く<code>Kernel#sleep</code>を実装できます。</p> <pre class="code lang-html" data-lang="html" data-unlink><span class="synIdentifier">&lt;</span><span class="synStatement">html</span><span class="synIdentifier">&gt;</span> <span class="synIdentifier">&lt;</span><span class="synStatement">head</span><span class="synIdentifier">&gt;</span> <span class="synPreProc"> </span><span class="synIdentifier">&lt;</span><span class="synStatement">title</span><span class="synIdentifier">&gt;</span>Kernel#sleep<span class="synIdentifier">&lt;/</span><span class="synStatement">title</span><span class="synIdentifier">&gt;</span> <span class="synPreProc"> </span><span class="synIdentifier">&lt;</span><span class="synStatement">script</span> <span class="synIdentifier"> </span><span class="synType">src</span><span class="synIdentifier">=</span><span class="synConstant">&quot;https://cdn.jsdelivr.net/npm/@ruby/head-wasm-wasi@2.4.1-2024-01-26-a/dist/browser.script.iife.js&quot;</span><span class="synIdentifier">&gt;&lt;/</span><span class="synStatement">script</span><span class="synIdentifier">&gt;</span> <span class="synPreProc"> </span><span class="synIdentifier">&lt;</span><span class="synStatement">script</span><span class="synIdentifier"> </span><span class="synType">type</span><span class="synIdentifier">=</span><span class="synConstant">&quot;text/ruby&quot;</span><span class="synIdentifier"> </span><span class="synType">data</span><span class="synIdentifier">-eval=</span><span class="synConstant">&quot;async&quot;</span><span class="synIdentifier">&gt;</span> <span class="synSpecial"> require </span><span class="synConstant">'js'</span> <span class="synSpecial"> module Kernel</span> <span class="synSpecial"> def sleep</span>(<span class="synSpecial">time</span>) <span class="synSpecial"> JS.eval</span>(<span class="synConstant">&quot;return new Promise((resolve) =&gt; setTimeout(resolve, time * 1000))&quot;</span>)<span class="synSpecial">.await</span> <span class="synSpecial"> end</span> <span class="synSpecial"> end</span> <span class="synSpecial"> start = JS.global</span><span class="synIdentifier">[</span><span class="synSpecial">:</span><span class="synType">Date</span><span class="synIdentifier">]</span><span class="synSpecial">.now.to_i</span> <span class="synSpecial"> sleep</span>(1) <span class="synSpecial"> puts </span><span class="synConstant">&quot;#{JS.global[:Date].now.to_i - start}ms passed&quot;</span> <span class="synSpecial"> </span><span class="synIdentifier">&lt;/</span><span class="synStatement">script</span><span class="synIdentifier">&gt;</span> <span class="synIdentifier">&lt;/</span><span class="synStatement">head</span><span class="synIdentifier">&gt;</span> <span class="synIdentifier">&lt;/</span><span class="synStatement">html</span><span class="synIdentifier">&gt;</span> </pre> <p>Promiseの初期化を次のように書いています。</p> <pre class="code lang-ruby" data-lang="ruby" data-unlink><span class="synType">JS</span>.eval(<span class="synSpecial">&quot;</span><span class="synConstant">return new Promise((resolve) =&gt; setTimeout(resolve, time * 1000))</span><span class="synSpecial">&quot;</span>).await </pre> <p>次のように<code>new</code>メソッドにコールバック関数をブロックで渡したいです。</p> <pre class="code lang-ruby" data-lang="ruby" data-unlink><span class="synType">JS</span>.global[<span class="synConstant">:Promise</span>].new <span class="synStatement">do</span> |resolve| <span class="synType">JS</span>.global.setTimeout(resolve, time * <span class="synConstant">1000</span>) <span class="synStatement">end</span>.await </pre> <p>これは次のエラーがでます。</p> <blockquote><p>TypeError: Promise resolver undefined is not a function</p></blockquote> <p>しかし<code>new</code>メソッドの定義はブロックを考慮していません。</p> <pre class="code lang-ruby" data-lang="ruby" data-unlink><span class="synPreProc">def</span> <span class="synIdentifier">new</span>(*args) <span class="synType">JS</span>.global[<span class="synConstant">:Reflect</span>].construct(<span class="synConstant">self</span>, args.to_js) <span class="synPreProc">end</span> </pre> <p>つまり次の<a class="keyword" href="https://d.hatena.ne.jp/keyword/JavaScript">JavaScript</a>を実行しているのと同じです。</p> <pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synStatement">new</span> Promise(<span class="synStatement">undefined</span>) </pre> <p>ですので、<code>resolver</code>引数が<code>undefined</code>であると怒られます。 では、次のようにしてはどうでしょうか?</p> <pre class="code lang-ruby" data-lang="ruby" data-unlink><span class="synPreProc">class</span> <span class="synType">JS</span>::<span class="synType">Object</span> <span class="synPreProc">def</span> <span class="synIdentifier">new</span>(*args, &amp;block) <span class="synType">JS</span>.global[<span class="synConstant">:Reflect</span>].construct(<span class="synConstant">self</span>, block) <span class="synPreProc">end</span> <span class="synPreProc">end</span> <span class="synPreProc">module</span> <span class="synType">Kernel</span> <span class="synPreProc">def</span> <span class="synIdentifier">sleep</span>(time) <span class="synType">JS</span>.global[<span class="synConstant">:Promise</span>].new <span class="synStatement">do</span> |resolve| <span class="synType">JS</span>.global.setTimeout(resolve, time * <span class="synConstant">1000</span>) <span class="synStatement">end</span>.await <span class="synPreProc">end</span> <span class="synPreProc">end</span> </pre> <p><code>new</code>メソッドでブロックを引数として渡します。 上手く行きそうですが、やはり同様のエラーが起きます。</p> <blockquote><p> TypeError: Promise resolver undefined is not a function</p></blockquote> <p>これは<a class="keyword" href="https://d.hatena.ne.jp/keyword/JavaScript">JavaScript</a>でかくと、次のようになります。</p> <pre class="code lang-javascript" data-lang="javascript" data-unlink>Reflect.construct(Promise, ()=&gt;<span class="synIdentifier">{}</span>) </pre> <p><a href="https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Reflect/construct">Reflect.construct</a>の第二引数には、配列風オブジェクトを渡す必要があります。 次のようにすると良さそうです。</p> <pre class="code lang-ruby" data-lang="ruby" data-unlink><span class="synPreProc">def</span> <span class="synIdentifier">new</span>(*args, &amp;block) <span class="synType">JS</span>.global[<span class="synConstant">:Reflect</span>].construct(<span class="synConstant">self</span>, [block]) <span class="synPreProc">end</span> </pre> <p>ひとまず動きます。 つぎのようにすると、<code>new</code>メソッドにブロックを渡したときも、引数を渡したときも<a class="keyword" href="https://d.hatena.ne.jp/keyword/JavaScript">JavaScript</a>の<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%B9%A5%C8%A5%E9%A5%AF%A5%BF%A1%BC">コンストラクター</a>を呼び出せるようになります。</p> <pre class="code lang-html" data-lang="html" data-unlink><span class="synIdentifier">&lt;</span><span class="synStatement">html</span><span class="synIdentifier">&gt;</span> <span class="synIdentifier">&lt;</span><span class="synStatement">head</span><span class="synIdentifier">&gt;</span> <span class="synPreProc"> </span><span class="synIdentifier">&lt;</span><span class="synStatement">title</span><span class="synIdentifier">&gt;</span>Kernel#sleep<span class="synIdentifier">&lt;/</span><span class="synStatement">title</span><span class="synIdentifier">&gt;</span> <span class="synPreProc"> </span><span class="synIdentifier">&lt;</span><span class="synStatement">script</span> <span class="synIdentifier"> </span><span class="synType">src</span><span class="synIdentifier">=</span><span class="synConstant">&quot;https://cdn.jsdelivr.net/npm/@ruby/head-wasm-wasi@2.4.1-2024-01-26-a/dist/browser.script.iife.js&quot;</span><span class="synIdentifier">&gt;&lt;/</span><span class="synStatement">script</span><span class="synIdentifier">&gt;</span> <span class="synPreProc"> </span><span class="synIdentifier">&lt;</span><span class="synStatement">script</span><span class="synIdentifier"> </span><span class="synType">type</span><span class="synIdentifier">=</span><span class="synConstant">&quot;text/ruby&quot;</span><span class="synIdentifier"> </span><span class="synType">data</span><span class="synIdentifier">-eval=</span><span class="synConstant">&quot;async&quot;</span><span class="synIdentifier">&gt;</span> <span class="synSpecial"> require </span><span class="synConstant">'js'</span> <span class="synSpecial"> </span><span class="synStatement">class</span><span class="synSpecial"> JS::</span><span class="synType">Object</span> <span class="synSpecial"> def </span><span class="synStatement">new</span>(<span class="synSpecial">*args, &amp;block</span>) <span class="synSpecial"> </span><span class="synStatement">if</span><span class="synSpecial"> block</span> <span class="synSpecial"> JS.global</span><span class="synIdentifier">[</span><span class="synSpecial">:Reflect</span><span class="synIdentifier">]</span><span class="synSpecial">.construct</span>(<span class="synStatement">self</span><span class="synSpecial">, </span><span class="synIdentifier">[</span><span class="synSpecial">block</span><span class="synIdentifier">]</span>) <span class="synSpecial"> </span><span class="synStatement">else</span> <span class="synSpecial"> JS.global</span><span class="synIdentifier">[</span><span class="synSpecial">:Reflect</span><span class="synIdentifier">]</span><span class="synSpecial">.construct</span>(<span class="synStatement">self</span><span class="synSpecial">, args</span>) <span class="synSpecial"> end</span> <span class="synSpecial"> end</span> <span class="synSpecial"> end</span> <span class="synSpecial"> module Kernel</span> <span class="synSpecial"> def sleep</span>(<span class="synSpecial">time</span>) <span class="synSpecial"> JS.global</span><span class="synIdentifier">[</span><span class="synSpecial">:Promise</span><span class="synIdentifier">]</span><span class="synSpecial">.</span><span class="synStatement">new</span><span class="synSpecial"> </span><span class="synStatement">do</span><span class="synSpecial"> |resolve|</span> <span class="synSpecial"> JS.global.setTimeout</span>(<span class="synSpecial">resolve, time * </span>1000) <span class="synSpecial"> end.await</span> <span class="synSpecial"> end</span> <span class="synSpecial"> end</span> <span class="synSpecial"> start = JS.global</span><span class="synIdentifier">[</span><span class="synSpecial">:</span><span class="synType">Date</span><span class="synIdentifier">]</span><span class="synSpecial">.now.to_i</span> <span class="synSpecial"> sleep</span>(1) <span class="synSpecial"> puts </span><span class="synConstant">&quot;#{JS.global[:Date].now.to_i - start}ms passed&quot;</span> <span class="synSpecial"> p JS.global</span><span class="synIdentifier">[</span><span class="synSpecial">:</span><span class="synType">Date</span><span class="synIdentifier">]</span><span class="synSpecial">.</span><span class="synStatement">new</span>(<span class="synConstant">&quot;2024-01-27T00:00:00Z&quot;</span>) <span class="synSpecial"> </span><span class="synIdentifier">&lt;/</span><span class="synStatement">script</span><span class="synIdentifier">&gt;</span> <span class="synIdentifier">&lt;/</span><span class="synStatement">head</span><span class="synIdentifier">&gt;</span> <span class="synIdentifier">&lt;/</span><span class="synStatement">html</span><span class="synIdentifier">&gt;</span> </pre> ledsun オートローダー設計調査 hatenablog://entry/6801883189076948391 2024-01-22T07:58:56+09:00 2024-01-22T07:58:56+09:00 ruby.wasmでオートロードする - @ledsun blog でModule#const_missingをつかってオートローダーを書いてみました。 Module#const_missingのAPIで、汎用的なオートローダーを書くのは少し難しそうなことがわかりました。 難しそうですが、どれくらい難しいのかよくわかっていません。 そこで、先人の知恵に頼ります。 具体的には、Rails 5.2 から6.0でオートローダーをclassicからzeitwerkに変更した出来事を調べます。 Rails 5.2までのオートローダーはModule#const_missingを使って実装されています。 R… <p><a href="https://ledsun.hatenablog.com/entry/2024/01/21/105544">ruby.wasm&#x3067;&#x30AA;&#x30FC;&#x30C8;&#x30ED;&#x30FC;&#x30C9;&#x3059;&#x308B; - @ledsun blog</a> で<a href="https://docs.ruby-lang.org/ja/latest/method/Module/i/const_missing.html">Module#const_missing</a>をつかってオートローダーを書いてみました。 <code>Module#const_missing</code>の<a class="keyword" href="https://d.hatena.ne.jp/keyword/API">API</a>で、汎用的なオートローダーを書くのは少し難しそうなことがわかりました。 難しそうですが、どれくらい難しいのかよくわかっていません。 そこで、先人の知恵に頼ります。</p> <p>具体的には、<a class="keyword" href="https://d.hatena.ne.jp/keyword/Rails">Rails</a> 5.2 から6.0でオートローダーをclassicから<a href="https://github.com/fxn/zeitwerk">zeitwerk</a>に変更した出来事を調べます。 <a class="keyword" href="https://d.hatena.ne.jp/keyword/Rails">Rails</a> 5.2までのオートローダーは<code>Module#const_missing</code>を使って実装されています。 <a class="keyword" href="https://d.hatena.ne.jp/keyword/Rails">Rails</a> 6.0以降のオートローダーzeitwerkは<a href="https://docs.ruby-lang.org/ja/latest/method/Kernel/m/autoload.html">Kernel.#autoload</a>を使って実装されています。 なぜでしょうか?</p> <h2 id="ClassicローダーからみるModuleconst_missingを使ったオートローダーの問題点">Classicローダーからみる<code>Module#const_missing</code>を使ったオートローダーの問題点</h2> <p>classicローダーの<code>Module#const_missing</code>を使った実装は、おおむね動いていましたが細かい問題がありました。 わかりやすい例は <a href="https://medium.com/cedarcode/understanding-zeitwerk-in-rails-6-f168a9f09a1f">Understanding Zeitwerk in Rails 6 | by Marcelo Casiraghi | Cedarcode | Medium</a> で、説明されています。</p> <p>次のような例が挙げられています。</p> <pre class="code lang-ruby" data-lang="ruby" data-unlink><span class="synComment"># app/models/user.rb</span> <span class="synPreProc">class</span> <span class="synType">User</span> &lt; <span class="synType">ApplicationRecord</span> <span class="synPreProc">end</span> <span class="synComment"># app/models/admin/user.rb</span> <span class="synPreProc">module</span> <span class="synType">Admin</span> <span class="synPreProc">class</span> <span class="synType">User</span> &lt; <span class="synType">ApplicationRecord</span> <span class="synPreProc">end</span> <span class="synPreProc">end</span> <span class="synComment"># app/models/admin/user_manager.rb</span> <span class="synPreProc">module</span> <span class="synType">Admin</span> <span class="synPreProc">class</span> <span class="synType">UserManager</span> <span class="synPreProc">def</span> <span class="synConstant">self</span>.<span class="synIdentifier">all</span> <span class="synType">User</span>.all <span class="synComment"># Want to load all admin users</span> <span class="synPreProc">end</span> <span class="synPreProc">end</span> <span class="synPreProc">end</span> </pre> <p>UserManager内でUserを呼び出したときに、<code>Module#const_missing</code>では、呼び出された物がUserかAdmin::Userかわかりません。 <code>Module#const_missing</code>の引数には、どちらの場合であってもシンボル<code>:User</code>が渡されます。</p> <p>その他の多くの問題が<a href="https://guides.rubyonrails.org/v5.2/autoloading_and_reloading_constants.html#common-gotchas">Rails 5.2のRailsガイド</a>で列挙されています。 <code>Module#const_missing</code>でオートローダーを実装するには、とても多くの問題があるようです。</p> <h2 id="Zeitwerkのとった解決方法">Zeitwerkのとった解決方法</h2> <p>これらの問題を解決するためにzeitwerkは<code>Kernel.#autoload</code>を使って再実装されました。 <code>Kernel.#autoload</code>はモジュールを読み込むパスを指定できます。 モジュールが必要になったときに指定したパスをつかって<code>Kernel.#autoload</code>します。 つまり、オートロードです。 正確にはオートロードに使うヒントを<a class="keyword" href="https://d.hatena.ne.jp/keyword/Ruby">Ruby</a> <a class="keyword" href="https://d.hatena.ne.jp/keyword/VM">VM</a>に渡します。</p> <p>zeitwerkでは、起動時に<a href="https://github.com/fxn/zeitwerk/blob/94e83ee90e14eee69a151a7b63e44bb58ade3ba3/lib/zeitwerk/loader.rb#L119">setup</a>メソッドを使ってルート<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リからモジュールのファイルパスを収集し、<code>Kernel.#autoload</code>を呼び出します。 <code>Kernel.#autoload</code>を使うには、モジュール呼び出しより前に、モジュールの実体ファイルのパスを調べます。</p> <h2 id="rubywasmへの応用を考える"><a class="keyword" href="https://d.hatena.ne.jp/keyword/ruby">ruby</a>.wasmへの応用を考える</h2> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/ruby">ruby</a>.wasmでは、この方式は厳しいです。 サーバーに対してパス探索をかけたくありません。 自動的にモジュールのパスを集めるのは難しいです。</p> <p>さらに前の段階でモジュールパスを調査しておいて、import mapsのようにブラウザに渡せばできるでしょう。 ですが、オートロードがうれしいのは設定が要らない点です。設定より規約(Convention over Configuration、CoC)です。 マップを作ってブラウザに渡すのは設定です。 <code>Kernel#require_relative</code>を各<a class="keyword" href="https://d.hatena.ne.jp/keyword/Ruby">Ruby</a><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%EA%A5%D7%A5%C8">スクリプト</a>ファイルに書くのと同じです。</p> <h2 id="参考">参考</h2> <ul> <li><a href="https://moneyforward-dev.jp/entry/2022/05/20/zeitwerk-and-require_dependency/">Zeitwerk&#x3068;require_dependency - Money Forward Developers Blog</a></li> <li><a href="https://qiita.com/fursich/items/717a720d9f4465e4cbbb">Zeitwerk&#x306E;&#x58CA;&#x3057;&#x65B9; #Ruby - Qiita</a></li> <li><a href="https://zenn.dev/murakamiiii/articles/893d83626c9f15">&#x3010;Rails&#x3011;Zeitwerk&#x306B;&#x3064;&#x3044;&#x3066;&#x8ABF;&#x3079;&#x305F;&#x3053;&#x3068;</a></li> </ul> ledsun ruby.wasmでオートロードする hatenablog://entry/6801883189076769175 2024-01-21T10:55:44+09:00 2024-01-21T16:05:53+09:00 kakikataというペン習字練習用紙を印刷するruby.wasmアプリケーションがあります。 ledsun.github.io 複数ページ印刷する機能を追加するついでに、requrie_relativeを使ってファイル分割していました。 やっている内に、単純作業が面倒臭くなってオートローダーが欲しくなりました。 const_missingを使えばできるはずと思って、ひとまず作ってみました。 簡単オートローダー https://github.com/ledsun/kakikata/blob/9fefffc518025fb559e4b5d7d5c07592b3f72a12/main.rb#L12… <p>kakikataというペン習字練習用紙を印刷する<a class="keyword" href="https://d.hatena.ne.jp/keyword/ruby">ruby</a>.wasmアプリケーションがあります。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fledsun.github.io%2Fkakikata%2F" title="kakikata" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://ledsun.github.io/kakikata/">ledsun.github.io</a></cite></p> <p>複数ページ印刷する機能を追加するついでに、<code>requrie_relative</code>を使ってファイル分割していました。 やっている内に、単純作業が面倒臭くなってオートローダーが欲しくなりました。 <code>const_missing</code>を使えばできるはずと思って、ひとまず作ってみました。</p> <h2 id="簡単オートローダー">簡単オートローダー</h2> <p><a href="https://github.com/ledsun/kakikata/blob/9fefffc518025fb559e4b5d7d5c07592b3f72a12/main.rb#L12-L31">https://github.com/ledsun/kakikata/blob/9fefffc518025fb559e4b5d7d5c07592b3f72a12/main.rb#L12-L31</a></p> <pre class="code lang-ruby" data-lang="ruby" data-unlink><span class="synComment"># 定数名からモジュールをオートーロードします。</span> <span class="synPreProc">def</span> <span class="synType">Object</span>.<span class="synIdentifier">const_missing</span>(id) module_name = id.to_snake_case <span class="synType">JS</span>::<span class="synType">RequireRemote</span>.instance.load(module_name) p <span class="synSpecial">&quot;#{</span>module_name<span class="synSpecial">}</span><span class="synConstant"> loaded!</span><span class="synSpecial">&quot;</span> mod = const_get(id) <span class="synComment"># 読み込んだモジュールに、サブモジュールのオートーロードを定義します。</span> mod.define_singleton_method(<span class="synConstant">:const_missing</span>) <span class="synStatement">do</span> |sub_id| path = <span class="synConstant">self</span>.name.to_s.split(<span class="synSpecial">'</span><span class="synConstant">::</span><span class="synSpecial">'</span>) .map(&amp;<span class="synConstant">:to_snake_case</span>) .join(<span class="synSpecial">'</span><span class="synConstant">/</span><span class="synSpecial">'</span>) module_name = <span class="synSpecial">&quot;#{</span>path<span class="synSpecial">}</span><span class="synConstant">/</span><span class="synSpecial">#{</span>sub_id.to_snake_case<span class="synSpecial">}&quot;</span> <span class="synType">JS</span>::<span class="synType">RequireRemote</span>.instance.load(module_name) p <span class="synSpecial">&quot;#{</span>module_name<span class="synSpecial">}</span><span class="synConstant"> loaded!</span><span class="synSpecial">&quot;</span> const_get(sub_id) <span class="synStatement">end</span> mod <span class="synPreProc">end</span> </pre> <p>初めて使う定数の読み出し時に<code>const_missing</code>が呼ばれます。 定数名から<a class="keyword" href="https://d.hatena.ne.jp/keyword/Ruby">Ruby</a><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%EA%A5%D7%A5%C8">スクリプト</a>のファイルパスを解決して読み込みます。 <a class="keyword" href="https://d.hatena.ne.jp/keyword/%CC%BE%C1%B0%B6%F5%B4%D6">名前空間</a>が<a class="keyword" href="https://d.hatena.ne.jp/keyword/%C6%FE%A4%EC%BB%D2">入れ子</a>になっている場合は、親モジュールの<code>const_missing</code>が呼ばれます。 モジュールを読み込んだときに、同様の<a class="keyword" href="https://d.hatena.ne.jp/keyword/Ruby">Ruby</a><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%EA%A5%D7%A5%C8">スクリプト</a>読込処理を定義しています。</p> <p>今回は比較的シンプルなアプリケーションなので、ルート<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リからのロードしか考慮していません。 <a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リ内の<a class="keyword" href="https://d.hatena.ne.jp/keyword/Ruby">Ruby</a><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%EA%A5%D7%A5%C8">スクリプト</a>からロードしようとすると、ミスるはずです。 <a class="keyword" href="https://d.hatena.ne.jp/keyword/%C0%E4%C2%D0%A5%D1%A5%B9">絶対パス</a>指定にすれば良いだけのような気もしますが、試していません。</p> <p>組み込みのも外部のもgemの読込は考慮していません。 erbをつかっていますが、手動で<code>require 'erb'</code>しています。 アプリケーションのコードをネーミングルールに従って読み込みます。</p> <p>これだけの行数でオートローダーがかける<a class="keyword" href="https://d.hatena.ne.jp/keyword/Ruby">Ruby</a>って、不思議な<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D7%A5%ED%A5%B0%A5%E9%A5%DF%A5%F3%A5%B0%B8%C0%B8%EC">プログラミング言語</a>ですね。</p> <h2 id="以前はオートローダーは考えてなかった">以前はオートローダーは考えてなかった</h2> <p>僕が、もともと想定していた、<a class="keyword" href="https://d.hatena.ne.jp/keyword/ruby">ruby</a>.wasmアプリケーションの作り方は、</p> <ol> <li>CRubyで大まかな動きを実装する</li> <li><a class="keyword" href="https://d.hatena.ne.jp/keyword/ruby">ruby</a>.wasmに移植する</li> </ol> <p>でした。 <a href="https://wordle-search.onrender.com/">Wordle Search</a>と<a href="https://github.com/ledsun/tetris">GitHub - ledsun/tetris: Ruby&#x3067;&#x30C6;&#x30C8;&#x30EA;&#x30B9;&#x3092;&#x5B9F;&#x88C5;&#x3059;&#x308B;</a>は、このパターンです。 このパターンでは、オートローダーは要らないと考えていました。</p> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/Rails">Rails</a>以外のCRubyアプリケーションを作る時に<code>zeitwerk</code>のようなオートローダーを使うのはあまり流行っていなさそうです。 <a class="keyword" href="https://d.hatena.ne.jp/keyword/Rails">Rails</a>を<a class="keyword" href="https://d.hatena.ne.jp/keyword/ruby">ruby</a>.wasmで動かしても、そんなに嬉しくなさそうです。 また、<code>zeitwerk</code>そのものを<a class="keyword" href="https://d.hatena.ne.jp/keyword/ruby">ruby</a>.wasmに持ってくるのは難しいです。 というわけで、オートローダーはオマケぐらいに考えていました。</p> <p>kakikataは、ブラウザで印刷するためのアプリケーションなので、最初から<a class="keyword" href="https://d.hatena.ne.jp/keyword/ruby">ruby</a>.wasm向けに作っています。 CRuby版は存在しません。 CRubyでの開発体験を気にせずに、<a class="keyword" href="https://d.hatena.ne.jp/keyword/ruby">ruby</a>.wasmではオートローダーが使えてもいいのかもしれません。</p> <h2 id="追記">追記</h2> <p>よくると、このオートローダーは、動きがちょっと変です。</p> <p><a href="https://github.com/ledsun/kakikata/blob/9fefffc518025fb559e4b5d7d5c07592b3f72a12/app/document.rb">https://github.com/ledsun/kakikata/blob/9fefffc518025fb559e4b5d7d5c07592b3f72a12/app/document.rb</a></p> <p>Documentはトッ<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D7%A5%EC%A5%D9">プレベ</a>ルのモジュールです。 Appモジュール内で呼び出されたので、App::Documentとして解釈しています。 <a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リがappなので解決できています。</p> <p>リモート呼び出しだと、スカって次の候補を探すのはあまりやりたくありません。 もうちょっとシンプルなモジュール名-パス解決ルールを決めないと、汎用的なオートローダーとするのは難しそうです。</p> ledsun 万年筆 hatenablog://entry/6801883189075038214 2024-01-14T18:10:39+09:00 2024-01-14T18:10:39+09:00 万年筆を買いました。 ペン習字をはじめた - @ledsun blog のの一環です。 練習するなら良い道具を使った方が気持ちが盛り上がります。 セーラー万年筆 万年筆 プロフィットスタンダード アイボリー 太字 11-1219-617セーラー万年筆Amazon 以前、パイロットの細字を使っていました。 今回は万年筆のヌルヌル感を求めて太字にしました。 色はアイボリーです。 なんとなく、女性向けラインのような気はします。 形がスタンダードなので、色までスタンダードだとつまらないので、アイボリーです。 実際に書いてみると、漢字はかなり大きく書かないと潰れます。 書き慣れていないので、バランス調整… <p>万年筆を買いました。 <a href="https://ledsun.hatenablog.com/entry/2024/01/02/140909">&#x30DA;&#x30F3;&#x7FD2;&#x5B57;&#x3092;&#x306F;&#x3058;&#x3081;&#x305F; - @ledsun blog</a> のの一環です。 練習するなら良い道具を使った方が気持ちが盛り上がります。</p> <p><div class="hatena-asin-detail"><a href="https://www.amazon.co.jp/dp/B08HMGTJ35?tag=ledsun-22&amp;linkCode=osi&amp;th=1&amp;psc=1" class="hatena-asin-detail-image-link" target="_blank" rel="noopener"><img src="https://m.media-amazon.com/images/I/31WdvxIDDUL._SL500_.jpg" class="hatena-asin-detail-image" alt="セーラー万年筆 万年筆 プロフィットスタンダード アイボリー 太字 11-1219-617" title="セーラー万年筆 万年筆 プロフィットスタンダード アイボリー 太字 11-1219-617"></a><div class="hatena-asin-detail-info"><p class="hatena-asin-detail-title"><a href="https://www.amazon.co.jp/dp/B08HMGTJ35?tag=ledsun-22&amp;linkCode=osi&amp;th=1&amp;psc=1" target="_blank" rel="noopener">セーラー万年筆 万年筆 プロフィットスタンダード アイボリー 太字 11-1219-617</a></p><ul class="hatena-asin-detail-meta"><li><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%BB%A1%BC%A5%E9%A1%BC%CB%FC%C7%AF%C9%AE">セーラー万年筆</a></li></ul><a href="https://www.amazon.co.jp/dp/B08HMGTJ35?tag=ledsun-22&amp;linkCode=osi&amp;th=1&amp;psc=1" class="asin-detail-buy" target="_blank" rel="noopener">Amazon</a></div></div></p> <p>以前、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D1%A5%A4%A5%ED">パイロ</a>ットの細字を使っていました。 今回は万年筆のヌルヌル感を求めて太字にしました。 色はアイボリーです。 なんとなく、女性向けラインのような気はします。 形がスタンダードなので、色までスタンダードだとつまらないので、アイボリーです。</p> <p>実際に書いてみると、漢字はかなり大きく書かないと潰れます。 書き慣れていないので、バランス調整が難しいです。</p> <p><figure class="figure-image figure-image-fotolife" title="万年筆を使って書いた文字の写真"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/l/ledsun/20240114/20240114180115.jpg" width="1200" height="904" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>万年筆を使って書いた文字の写真</figcaption></figure></p> <p>内容は<a class="keyword" href="https://d.hatena.ne.jp/keyword/%C3%A4%C7%AF">辰年</a>にちなんだ書き初めです。 サビは慣れちゃっているのでそうでもないのですが、すごいフレーズです。</p> <blockquote><p>愛遠き世界にひるがえす 純情にも似た欲望よ<br> 銀色の月に照らされたオマエのEYEが怖いほど</p></blockquote> <p>文の意味はよくわかりませんが、あふれ出る中2感が素晴らしいです。 何をどうやったらこんな詞が書けるのでしょうか?</p> ledsun rustのWASI用のThreadのsleep関数を読む hatenablog://entry/6801883189074059764 2024-01-10T23:22:36+09:00 2024-01-10T23:22:36+09:00 https://github.com/rust-lang/rust/blob/e9271846294c4ee5bd7706df68180320c0b5ff20/library/std/src/sys/wasi/thread.rs#L137 *1 pub fn sleep(dur: Duration) { let nanos = dur.as_nanos(); assert!(nanos <= u64::MAX as u128); const USERDATA: wasi::Userdata = 0x0123_45678; let clock = wasi::SubscriptionClock … <p><a href="https://github.com/rust-lang/rust/blob/e9271846294c4ee5bd7706df68180320c0b5ff20/library/std/src/sys/wasi/thread.rs#L137">https://github.com/rust-lang/rust/blob/e9271846294c4ee5bd7706df68180320c0b5ff20/library/std/src/sys/wasi/thread.rs#L137</a> <a href="#f-87e24b22" id="fn-87e24b22" name="fn-87e24b22" title="前回の記事の後により新しいバージョンを発見しました。">*1</a></p> <pre class="code lang-rust" data-lang="rust" data-unlink> <span class="synStatement">pub</span> <span class="synStatement">fn</span> <span class="synIdentifier">sleep</span>(dur: Duration) { <span class="synStatement">let</span> nanos <span class="synStatement">=</span> dur.<span class="synIdentifier">as_nanos</span>(); <span class="synPreProc">assert!</span>(nanos <span class="synStatement">&lt;=</span> <span class="synType">u64</span><span class="synSpecial">::</span>MAX <span class="synStatement">as</span> <span class="synType">u128</span>); <span class="synType">const</span> USERDATA: <span class="synPreProc">wasi</span><span class="synSpecial">::</span>Userdata <span class="synStatement">=</span> <span class="synConstant">0x0123_45678</span>; <span class="synStatement">let</span> clock <span class="synStatement">=</span> <span class="synPreProc">wasi</span><span class="synSpecial">::</span>SubscriptionClock { id: <span class="synPreProc">wasi</span><span class="synSpecial">::</span>CLOCKID_MONOTONIC, timeout: nanos <span class="synStatement">as</span> <span class="synType">u64</span>, precision: <span class="synConstant">0</span>, flags: <span class="synConstant">0</span>, }; <span class="synStatement">let</span> in_ <span class="synStatement">=</span> <span class="synPreProc">wasi</span><span class="synSpecial">::</span>Subscription { userdata: USERDATA, u: <span class="synPreProc">wasi</span><span class="synSpecial">::</span>SubscriptionU { tag: <span class="synConstant">0</span>, u: <span class="synPreProc">wasi</span><span class="synSpecial">::</span>SubscriptionUU { clock } }, }; <span class="synStatement">unsafe</span> { <span class="synStatement">let</span> <span class="synType">mut</span> event: <span class="synPreProc">wasi</span><span class="synSpecial">::</span>Event <span class="synStatement">=</span> <span class="synPreProc">mem</span><span class="synSpecial">::</span><span class="synIdentifier">zeroed</span>(); <span class="synStatement">let</span> res <span class="synStatement">=</span> <span class="synPreProc">wasi</span><span class="synSpecial">::</span><span class="synIdentifier">poll_oneoff</span>(<span class="synType">&amp;</span>in_, <span class="synType">&amp;mut</span> event, <span class="synConstant">1</span>); <span class="synStatement">match</span> (res, event) { ( <span class="synConstant">Ok</span>(<span class="synConstant">1</span>), <span class="synPreProc">wasi</span><span class="synSpecial">::</span>Event { userdata: USERDATA, error: <span class="synPreProc">wasi</span><span class="synSpecial">::</span>ERRNO_SUCCESS, type_: <span class="synPreProc">wasi</span><span class="synSpecial">::</span>EVENTTYPE_CLOCK, .. }, ) <span class="synStatement">=&gt;</span> {} _ <span class="synStatement">=&gt;</span> <span class="synPreProc">panic!</span>(<span class="synConstant">&quot;thread::sleep(): unexpected result of poll_oneoff&quot;</span>), } } } </pre> <p>このファイルが何かはあまりよくわかっていません。 <a href="https://github.com/rust-lang/rust/tree/e9271846294c4ee5bd7706df68180320c0b5ff20/library/std/src/sys">src/sys</a><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リに<code>unix</code>や<code>windows</code>があります。 rustでク<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%ED%A5%B9%A5%B3%A5%F3">ロスコン</a>パイルするときに、ターゲットOSに合わせて埋め込まれる実装だろうと思っています。</p> <p>なんとなく読み方がわかったのでメモしておきます。</p> <p><a href="https://docs.rs/wasi/latest/wasi/fn.poll_oneoff.html">poll_oneoff in wasi - Rust</a> に<code>poll_oneoff</code>関数のリファレンスがあります。</p> <pre class="code lang-rust" data-lang="rust" data-unlink><span class="synStatement">pub</span> <span class="synStatement">unsafe</span> <span class="synStatement">fn</span> <span class="synIdentifier">poll_oneoff</span>( in_: <span class="synType">*const</span> Subscription, out: <span class="synType">*mut</span> Event, nsubscriptions: Size ) <span class="synStatement">-&gt;</span> <span class="synType">Result</span><span class="synStatement">&lt;</span>Size, Errno<span class="synStatement">&gt;</span> </pre> <p>実際の呼び出しが</p> <pre class="code lang-rust" data-lang="rust" data-unlink><span class="synPreProc">wasi</span><span class="synSpecial">::</span><span class="synIdentifier">poll_oneoff</span>(<span class="synType">&amp;</span>in_, <span class="synType">&amp;mut</span> event, <span class="synConstant">1</span>); </pre> <p>です。引数を3つ受け取っています。</p> <ol> <li>_in</li> <li>event</li> <li>1</li> </ol> <p><code>_in</code>は少し上で定義している構造体です。</p> <pre class="code lang-rust" data-lang="rust" data-unlink><span class="synStatement">let</span> in_ <span class="synStatement">=</span> <span class="synPreProc">wasi</span><span class="synSpecial">::</span>Subscription { userdata: USERDATA, u: <span class="synPreProc">wasi</span><span class="synSpecial">::</span>SubscriptionU { tag: <span class="synConstant">0</span>, u: <span class="synPreProc">wasi</span><span class="synSpecial">::</span>SubscriptionUU { clock } }, }; </pre> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/API">API</a>リファレンスと<code>Subscription</code>という型名が一致しています。 <code>Subscription</code>には<code>SubscriptionU</code>と言う構造体が入っていて、さらにその中に<code>SubscriptionUU</code>構造体があります。 その中に<code>clock</code>が入ります。 <code>clock</code>は</p> <pre class="code lang-rust" data-lang="rust" data-unlink><span class="synStatement">let</span> clock <span class="synStatement">=</span> <span class="synPreProc">wasi</span><span class="synSpecial">::</span>SubscriptionClock { id: <span class="synPreProc">wasi</span><span class="synSpecial">::</span>CLOCKID_MONOTONIC, timeout: nanos <span class="synStatement">as</span> <span class="synType">u64</span>, precision: <span class="synConstant">0</span>, flags: <span class="synConstant">0</span>, }; </pre> <p>です。 要するにこの関数は<code>poll_oneoff</code>を呼ぶのに必要なデータを作って、<code>poll_oneoff</code>を呼び出しています。 <code>CLOCKID_MONOTONIC</code>というのは</p> <p><a href="https://qiita.com/ozaki-r/items/fb4a48c2833e4b479ae1">CLOCK_REALTIME&#x3068;CLOCK_MONOTONIC #Linux - Qiita</a></p> <blockquote><p>CLOCK_MONOTONIC 時刻はかならず単調増加する システムの時刻変更の影響を受けるが、大きく変化することはないし、時間が戻ったりもしない</p></blockquote> <p>経過時間をみる指定のようです。 <code>timeout: nanos as u64</code>が<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%BF%A5%A4%A5%E0%A5%A2%A5%A6%A5%C8">タイムアウト</a>までの<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%CA%A5%CE%C9%C3">ナノ秒</a>を指定しているようです。</p> <p><code>poll_oneoff</code>関数の戻り値の処理は</p> <pre class="code lang-rust" data-lang="rust" data-unlink><span class="synStatement">match</span> (res, event) { ( <span class="synConstant">Ok</span>(<span class="synConstant">1</span>), <span class="synPreProc">wasi</span><span class="synSpecial">::</span>Event { userdata: USERDATA, error: <span class="synPreProc">wasi</span><span class="synSpecial">::</span>ERRNO_SUCCESS, type_: <span class="synPreProc">wasi</span><span class="synSpecial">::</span>EVENTTYPE_CLOCK, .. }, ) <span class="synStatement">=&gt;</span> {} _ <span class="synStatement">=&gt;</span> <span class="synPreProc">panic!</span>(<span class="synConstant">&quot;thread::sleep(): unexpected result of poll_oneoff&quot;</span>), } </pre> <p>パターンマッチで成功か失敗を判定して、失敗したら例外をあげているようです。</p> <p>おそらくこれと似たような関数を実装して、<code>Kernel#sleep</code>を置き換えるgemを作れば、<a class="keyword" href="https://d.hatena.ne.jp/keyword/ruby">ruby</a>.wasmで<code>Kernel#sleep</code>が使えるようになるはずです。 そうそう<a class="keyword" href="https://d.hatena.ne.jp/keyword/ruby">ruby</a>.wasmはごく最近、gemをwasmバイナリにpackできるようになりました<a href="#f-601191ea" id="fn-601191ea" name="fn-601191ea" title="https://github.com/ruby/ruby.wasm/pull/358">*2</a>。 さて、<a class="keyword" href="https://d.hatena.ne.jp/keyword/Ruby">Ruby</a>のRust拡張ってどうやって作ればいいのでしょうか?</p> <div class="footnote"> <p class="footnote"><a href="#fn-87e24b22" id="f-87e24b22" name="f-87e24b22" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">前回の記事の後により新しいバージョンを発見しました。</span></p> <p class="footnote"><a href="#fn-601191ea" id="f-601191ea" name="f-601191ea" class="footnote-number">*2</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a href="https://github.com/ruby/ruby.wasm/pull/358">https://github.com/ruby/ruby.wasm/pull/358</a></span></p> </div> ledsun ruby.wasmのKernel#sleepをどう実装したものか? hatenablog://entry/6801883189073761146 2024-01-09T20:36:25+09:00 2024-01-09T20:36:25+09:00 ruby.wasmを使ってブラウザ上でKernel#sleepを呼ぶとエラーが起きます。 <html> <body> <script src="https://cdn.jsdelivr.net/npm/@ruby/head-wasm-wasi@2.4.1-2024-01-05-a/dist/browser.script.iife.js"></script> <script type="text/ruby"> sleep 1 </script> </body> </html> エラーのスクリーンショット とりあえずこんなパッチを当てれば動くことはわかっています。 module Kernel de… <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/ruby">ruby</a>.wasmを使ってブラウザ上で<code>Kernel#sleep</code>を呼ぶとエラーが起きます。</p> <pre class="code lang-html" data-lang="html" data-unlink><span class="synIdentifier">&lt;</span><span class="synStatement">html</span><span class="synIdentifier">&gt;</span> <span class="synIdentifier">&lt;</span><span class="synStatement">body</span><span class="synIdentifier">&gt;</span> <span class="synIdentifier">&lt;</span><span class="synStatement">script</span> <span class="synIdentifier"> </span><span class="synType">src</span><span class="synIdentifier">=</span><span class="synConstant">&quot;https://cdn.jsdelivr.net/npm/@ruby/head-wasm-wasi@2.4.1-2024-01-05-a/dist/browser.script.iife.js&quot;</span><span class="synIdentifier">&gt;&lt;/</span><span class="synStatement">script</span><span class="synIdentifier">&gt;</span> <span class="synIdentifier">&lt;</span><span class="synStatement">script</span><span class="synIdentifier"> </span><span class="synType">type</span><span class="synIdentifier">=</span><span class="synConstant">&quot;text/ruby&quot;</span><span class="synIdentifier">&gt;</span> <span class="synSpecial"> sleep </span>1 <span class="synSpecial"> </span><span class="synIdentifier">&lt;/</span><span class="synStatement">script</span><span class="synIdentifier">&gt;</span> <span class="synIdentifier">&lt;/</span><span class="synStatement">body</span><span class="synIdentifier">&gt;</span> <span class="synIdentifier">&lt;/</span><span class="synStatement">html</span><span class="synIdentifier">&gt;</span> </pre> <p><figure class="figure-image figure-image-fotolife" title="エラーのスクリーンショット"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/l/ledsun/20240109/20240109201512.png" width="717" height="246" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>エラーの<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%EA%A1%BC%A5%F3%A5%B7%A5%E7%A5%C3%A5%C8">スクリーンショット</a></figcaption></figure></p> <p>とりあえずこんなパッチを当てれば動くことはわかっています。</p> <pre class="code lang-ruby" data-lang="ruby" data-unlink><span class="synPreProc">module</span> <span class="synType">Kernel</span> <span class="synPreProc">def</span> <span class="synIdentifier">sleep</span>(time) <span class="synType">JS</span>.eval(<span class="synSpecial">&quot;</span><span class="synConstant">return new Promise((resolve) =&gt; setTimeout(resolve, </span><span class="synSpecial">#{</span>time * <span class="synConstant">1000</span><span class="synSpecial">}</span><span class="synConstant">))</span><span class="synSpecial">&quot;</span>).await <span class="synPreProc">end</span> <span class="synPreProc">end</span> </pre> <p>現状<code>Promise#new</code>にコールバックをブロックで渡せないので、<code>JS.eval</code>を使っています。 これは直せると思います。</p> <p>もう一つ悩みがあります。 <a class="keyword" href="https://d.hatena.ne.jp/keyword/JavaScript">JavaScript</a>の<code>setTimeout</code>を使っています。 WASIのインターフェースを使ったほうがポータブルになりそうな気がします。</p> <p><a href="https://github.com/WebAssembly/WASI/issues/77">Add a sleep function to the Core &middot; Issue #77 &middot; WebAssembly/WASI &middot; GitHub</a> によると</p> <blockquote><p>I'm the current <a class="keyword" href="https://d.hatena.ne.jp/keyword/API">API</a>, the way to implement sleep is to use poll_oneoff, polling for a single __WASI_EVENTTYPE_CLOCK event.</p></blockquote> <p><code>poll_oneoff</code>という関数があるようです。</p> <p><a href="https://github.com/newpavlov/rust/blob/1e2b711d308be714e6211c125b1a33ac1247f866/src/libstd/sys/wasi/thread.rs#L30-L63">https://github.com/newpavlov/rust/blob/1e2b711d308be714e6211c125b1a33ac1247f866/src/libstd/sys/wasi/thread.rs#L30-L63</a>をみると、rustではWASI用の<code>Thread.sleep</code>を<code>poll_oneoff</code>関数を使って実装しているようです。</p> <p>これらの情報をみても、ruyb.wasmにどうやって実装したらいいのか皆目見当もつきません。</p> ledsun Rubyでテトリスを実装する その9 hatenablog://entry/6801883189072599436 2024-01-05T18:46:42+09:00 2024-01-05T18:46:42+09:00 Rubyでテトリスを実装する その8 - @ledsun blog の続きです。 すべてのテトリミノを実装しリファクタリングしました。 github.com CLIバージョンはひとまず完成です。 <p><a href="https://ledsun.hatenablog.com/entry/2024/01/03/221446">Ruby&#x3067;&#x30C6;&#x30C8;&#x30EA;&#x30B9;&#x3092;&#x5B9F;&#x88C5;&#x3059;&#x308B; &#x305D;&#x306E;8 - @ledsun blog</a> の続きです。 すべてのテトリミノを実装し<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%EA%A5%D5%A5%A1%A5%AF%A5%BF%A5%EA%A5%F3%A5%B0">リファクタリング</a>しました。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Fledsun%2Ftetoris%2Ftree%2Fcli" title="GitHub - ledsun/tetoris at cli" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://github.com/ledsun/tetoris/tree/cli">github.com</a></cite></p> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/CLI">CLI</a>バージョンはひとまず完成です。</p> ledsun Rubyでテトリスを実装する その8 hatenablog://entry/6801883189072092974 2024-01-03T22:14:46+09:00 2024-01-03T22:14:46+09:00 Rubyでテトリスを実装する その7 - @ledsun blog の続きです。 今回は 行を消すロジックの修正 T字形テトリミノの追加 デバッグ表示の追加 です。 行を消すロジックの修正 より宣言的にならないか工夫してみました。 https://github.com/ledsun/tetoris/blob/e5bb87a3c6a24049e8b3ea973a5965c25b5d643b/lib/field.rb#L34-L44 filled_rows = in_field_rows.select { |row| row.filled? } return if filled_rows.empt… <p><a href="https://ledsun.hatenablog.com/entry/2023/12/31/162612">Ruby&#x3067;&#x30C6;&#x30C8;&#x30EA;&#x30B9;&#x3092;&#x5B9F;&#x88C5;&#x3059;&#x308B; &#x305D;&#x306E;7 - @ledsun blog</a> の続きです。</p> <p>今回は</p> <ul> <li>行を消すロジックの修正</li> <li>T字形テトリミノの追加</li> <li><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C7%A5%D0%A5%C3%A5%B0">デバッグ</a>表示の追加</li> </ul> <p>です。</p> <h2 id="行を消すロジックの修正">行を消すロジックの修正</h2> <p>より宣言的にならないか工夫してみました。</p> <p><a href="https://github.com/ledsun/tetoris/blob/e5bb87a3c6a24049e8b3ea973a5965c25b5d643b/lib/field.rb#L34-L44">https://github.com/ledsun/tetoris/blob/e5bb87a3c6a24049e8b3ea973a5965c25b5d643b/lib/field.rb#L34-L44</a></p> <pre class="code lang-ruby" data-lang="ruby" data-unlink> filled_rows = in_field_rows.select { |row| row.filled? } <span class="synStatement">return</span> <span class="synStatement">if</span> filled_rows.empty? <span class="synComment"># 埋まった行を消す</span> filled_rows.each { |row| row.clear! } <span class="synComment"># 消した行より上の、インフィールド内のすべてのブロックを消した行分下にずらす</span> <span class="synComment"># 下から順番にずらす</span> <span class="synComment"># 上からずらすと、ずらしたブロックがずらす前のブロックと衝突してしまう</span> y = filled_rows.last.y in_field_blocks_over(y).reverse.each { |block| block.down filled_rows.size, <span class="synConstant">self</span> } </pre> <h2 id="T字型テトリミノの追加">T字型テトリミノの追加</h2> <p><a href="https://gyazo.com/b66e020c6ede806e9d7ccaf11815e480"><video width="668" autoplay muted loop playsinline controls><source src="https://i.gyazo.com/b66e020c6ede806e9d7ccaf11815e480.mp4" type="video/mp4"/></video></a></p> <p>他のテトリミノも実装予定です。</p> <h2 id="デバッグ表示の追加"><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C7%A5%D0%A5%C3%A5%B0">デバッグ</a>表示の追加</h2> <p>やはり<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C7%A5%D0%A5%C3%A5%B0">デバッグ</a>が難しいです。 例外時に盤面の情報を出せるようにしてみました。</p> <p><figure class="figure-image figure-image-fotolife" title="例外時に盤面の情報を出力しているスクリーンショット"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/l/ledsun/20240103/20240103220942.png" width="1175" height="604" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>例外時に盤面の情報を出力している<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%EA%A1%BC%A5%F3%A5%B7%A5%E7%A5%C3%A5%C8">スクリーンショット</a></figcaption></figure></p> ledsun ペン習字をはじめた hatenablog://entry/6801883189071732781 2024-01-02T14:09:09+09:00 2024-01-02T14:09:09+09:00 私は字が汚いです。 理由はわかっています。 脳内に収納している字形の解像度が低すぎるのです。 空で字を書くと、脳内の間違ったモデルをお手本に書くので、必ず汚い字が書けます。 克服するためには、脳に正しい字形をインプットする必要があります。 そこでペン習字です。 正しい字形をなぞって、正しい字形を覚えます。 絵を描くときのデッサンに似ています。 手を動かす訓練ではなく、字がどういう形なのかを見る解像度を上げる訓練です。 ところで、kakikataという便利なアプリケーションがあります。 ledsun.github.io 入力した任意の日本語をペン習字フォーマットで印刷できます。 これを使えば、ペ… <p>私は字が汚いです。 理由はわかっています。 脳内に収納している字形の解像度が低すぎるのです。 空で字を書くと、脳内の間違ったモデルをお手本に書くので、必ず汚い字が書けます。</p> <p>克服するためには、脳に正しい字形をインプットする必要があります。 そこでペン習字です。 正しい字形をなぞって、正しい字形を覚えます。 絵を描くときのデッサンに似ています。 手を動かす訓練ではなく、字がどういう形なのかを見る解像度を上げる訓練です。</p> <p>ところで、kakikataという便利なアプリケーションがあります。 <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fledsun.github.io%2Fkakikata%2F" title="kakikata" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://ledsun.github.io/kakikata/">ledsun.github.io</a></cite> 入力した任意の日本語をペン習字フォーマットで印刷できます。 これを使えば、ペン習字の教材を無限に生成できます。</p> <p>しかも、自分が気に入った文章で練習できるので、テンションアゲアゲです。</p> <p><figure class="figure-image figure-image-fotolife" title="気に入った文章を印刷したもの"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/l/ledsun/20240102/20240102124000.jpg" width="904" height="1200" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>気に入った文章を印刷したもの</figcaption></figure></p> <p>そういえばこのアプリケーションは<a class="keyword" href="https://d.hatena.ne.jp/keyword/ruby">ruby</a>.wasmでできているらしいです。 <a class="keyword" href="https://d.hatena.ne.jp/keyword/ruby">ruby</a>.wasm便利ですね。</p> ledsun Rubyでテトリスを実装する その7 hatenablog://entry/6801883189071266280 2023-12-31T16:26:12+09:00 2023-12-31T16:26:12+09:00 Rubyでテトリスを実装する その6 - @ledsun blog の続きです。 今回は、ライン消しです。 https://github.com/ledsun/tetoris/commit/fb4fd5d11f58d7353d4501124290574206a98dbf がコミットです。 行単位の処理が増えたので、FieldクラスからRowクラスを分離しました。 1行消すのは簡単です。 その後上のブロックを1段ずつ落としてくるところが難しかったです。 cursesを使っているので、デバッグプリントが出来ません。 上手く動かないときに原因を見つけるのが難しいです。 たぶん、デバッガを上手く使うと… <p><a href="https://ledsun.hatenablog.com/entry/2023/12/30/231719">Ruby&#x3067;&#x30C6;&#x30C8;&#x30EA;&#x30B9;&#x3092;&#x5B9F;&#x88C5;&#x3059;&#x308B; &#x305D;&#x306E;6 - @ledsun blog</a> の続きです。</p> <p>今回は、ライン消しです。 <a href="https://github.com/ledsun/tetoris/commit/fb4fd5d11f58d7353d4501124290574206a98dbf">https://github.com/ledsun/tetoris/commit/fb4fd5d11f58d7353d4501124290574206a98dbf</a> がコミットです。</p> <p>行単位の処理が増えたので、FieldクラスからRowクラスを分離しました。</p> <p>1行消すのは簡単です。 その後上のブロックを1段ずつ落としてくるところが難しかったです。 <a class="keyword" href="https://d.hatena.ne.jp/keyword/curses">curses</a>を使っているので、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C7%A5%D0%A5%C3%A5%B0">デバッグ</a>プリントが出来ません。 上手く動かないときに原因を見つけるのが難しいです。 たぶん、デバッガを上手く使うともう少し効率的に進められるのだと思います。</p> <p><a href="https://gyazo.com/bfb0fc1bd1ce443c33c450c8451b8388"><video width="646" autoplay muted loop playsinline controls><source src="https://i.gyazo.com/bfb0fc1bd1ce443c33c450c8451b8388.mp4" type="video/mp4"/></video></a></p> ledsun Rubyでテトリスを実装する その6 hatenablog://entry/6801883189071071669 2023-12-30T23:17:19+09:00 2023-12-30T23:17:19+09:00 Rubyでテトリスを実装する その5 - @ledsun blog の続きです。 左右キー下キーでテトリミノを移動出来るようにしました。 https://github.com/ledsun/tetoris/commit/bda62edefc2d13cdb6c93c242fc97b2729526313 が、コミットです。 実装は簡単でした。 落下と同様です。 移動し、衝突していたら戻します。 ここまでくると、とてもゲームっぽくなりました。 残念ながら、一行揃えても消えません。 <p><a href="https://ledsun.hatenablog.com/entry/2023/12/30/225355">Ruby&#x3067;&#x30C6;&#x30C8;&#x30EA;&#x30B9;&#x3092;&#x5B9F;&#x88C5;&#x3059;&#x308B; &#x305D;&#x306E;5 - @ledsun blog</a> の続きです。 左右キー下キーでテトリミノを移動出来るようにしました。</p> <p><a href="https://github.com/ledsun/tetoris/commit/bda62edefc2d13cdb6c93c242fc97b2729526313">https://github.com/ledsun/tetoris/commit/bda62edefc2d13cdb6c93c242fc97b2729526313</a> が、コミットです。 実装は簡単でした。 落下と同様です。 移動し、衝突していたら戻します。</p> <p><a href="https://gyazo.com/11b2d297593996716eafca8846a1efaa"><video width="486" autoplay muted loop playsinline controls><source src="https://i.gyazo.com/11b2d297593996716eafca8846a1efaa.mp4" type="video/mp4"/></video></a></p> <p>ここまでくると、とてもゲームっぽくなりました。 残念ながら、一行揃えても消えません。</p> ledsun Rubyでテトリスを実装する その5 hatenablog://entry/6801883189071064946 2023-12-30T22:53:55+09:00 2023-12-30T22:53:55+09:00 Rubyでテトリスを実装する その4 - @ledsun blog の続きです。 今回はゲームオーバーとリスタートを実装しました。 https://github.com/ledsun/tetoris/commit/0a19308d3ad2c0deef31ab01a73d77c214054d6c がコミットです。 <p><a href="https://ledsun.hatenablog.com/entry/2023/12/30/211117">Ruby&#x3067;&#x30C6;&#x30C8;&#x30EA;&#x30B9;&#x3092;&#x5B9F;&#x88C5;&#x3059;&#x308B; &#x305D;&#x306E;4 - @ledsun blog</a> の続きです。 今回はゲームオーバーとリスタートを実装しました。</p> <p><a href="https://github.com/ledsun/tetoris/commit/0a19308d3ad2c0deef31ab01a73d77c214054d6c">https://github.com/ledsun/tetoris/commit/0a19308d3ad2c0deef31ab01a73d77c214054d6c</a> がコミットです。</p> <p><a href="https://gyazo.com/b1d696e1299124eebd86d32a9f838f3a"><video width="600" autoplay muted loop playsinline controls><source src="https://i.gyazo.com/b1d696e1299124eebd86d32a9f838f3a.mp4" type="video/mp4"/></video></a></p> ledsun Rubyでテトリスを実装する その4 hatenablog://entry/6801883189071036437 2023-12-30T21:11:17+09:00 2023-12-30T21:11:17+09:00 Rubyでテトリスを実装する その3 - @ledsun blog の続きです。 今回はテトリミノの種類を増やします。 同時に、テトリミノが着地したときに新しいテトリミノを追加します。 着地しとテトリミノと新しいテトリミノが衝突する判定も必要なので、それもいれます。 実態としては、テトリミノに含まれているブロックを、フィールド(盤面)に移管します。 衝突判定ロジックは既存のままです。 https://github.com/ledsun/tetoris/commit/5e6308ecf6460991b9085c98ba7fd00c5dd9cddd がコミットです。 ゲームオーバーの判定が入ってい… <p><a href="https://ledsun.hatenablog.com/entry/2023/12/30/203623">Ruby&#x3067;&#x30C6;&#x30C8;&#x30EA;&#x30B9;&#x3092;&#x5B9F;&#x88C5;&#x3059;&#x308B; &#x305D;&#x306E;3 - @ledsun blog</a> の続きです。 今回はテトリミノの種類を増やします。 同時に、テトリミノが着地したときに新しいテトリミノを追加します。</p> <p>着地しとテトリミノと新しいテトリミノが衝突する判定も必要なので、それもいれます。 実態としては、テトリミノに含まれているブロックを、フィールド(盤面)に移管します。 衝突判定ロジックは既存のままです。</p> <p><a href="https://github.com/ledsun/tetoris/commit/5e6308ecf6460991b9085c98ba7fd00c5dd9cddd">https://github.com/ledsun/tetoris/commit/5e6308ecf6460991b9085c98ba7fd00c5dd9cddd</a> がコミットです。</p> <p><a href="https://gyazo.com/e00c2ac8e700895e83c9feaf29377ad1"><video width="638" autoplay muted loop playsinline controls><source src="https://i.gyazo.com/e00c2ac8e700895e83c9feaf29377ad1.mp4" type="video/mp4"/></video></a></p> <p>ゲームオーバーの判定が入っていません。 テトリミノが上まで行っても新しいテトリミノを追加しようとします。</p> ledsun Rubyでテトリスを実装する その3 hatenablog://entry/6801883189071027170 2023-12-30T20:36:23+09:00 2023-12-30T20:36:23+09:00 Rubyでテトリスを動かす その2 - @ledsun blog の続きです。 前回、I字型テトリミノが落ちてくるようになりました。 今回は、上キーの入力でテトリミノを回転します。 変更は https://github.com/ledsun/tetoris/commit/f93310b1254d2b53f8ca27ac0a15a373b95d8d01 です。 Rubyで行列を回転するのは@shape_map.transpose.map(&:reverse)で終わりました。 簡単でびっくりします。 Curses.stdscr.keypad(true)を呼ばないと、上キーの入力を拾えないところで躓… <p><a href="https://ledsun.hatenablog.com/entry/2023/12/30/200301">Ruby&#x3067;&#x30C6;&#x30C8;&#x30EA;&#x30B9;&#x3092;&#x52D5;&#x304B;&#x3059; &#x305D;&#x306E;2 - @ledsun blog</a> の続きです。 前回、I字型テトリミノが落ちてくるようになりました。 今回は、上キーの入力でテトリミノを回転します。</p> <p>変更は <a href="https://github.com/ledsun/tetoris/commit/f93310b1254d2b53f8ca27ac0a15a373b95d8d01">https://github.com/ledsun/tetoris/commit/f93310b1254d2b53f8ca27ac0a15a373b95d8d01</a> です。 <a class="keyword" href="https://d.hatena.ne.jp/keyword/Ruby">Ruby</a>で行列を回転するのは<code>@shape_map.transpose.map(&amp;:reverse)</code>で終わりました。 簡単でびっくりします。</p> <p><code>Curses.stdscr.keypad(true)</code>を呼ばないと、上キーの入力を拾えないところで躓きました。</p> <p><a href="https://gyazo.com/d6a8e12b0ef02f6f1717f3ccc45fa05f"><video width="546" autoplay muted loop playsinline controls><source src="https://i.gyazo.com/d6a8e12b0ef02f6f1717f3ccc45fa05f.mp4" type="video/mp4"/></video></a></p> ledsun Rubyでテトリスを実装する その2 hatenablog://entry/6801883189071019561 2023-12-30T20:03:01+09:00 2023-12-30T20:36:55+09:00 Rubyでテトリスを動かす その1 - @ledsun blog の続きです。 前回、枠を表示しました。 今回は、I字型テトリミノを表示して落下させます。 https://github.com/ledsun/tetoris/commit/9000667616f82486fe55c20e2c42b9c29e41bf34 の1コミットにまとめてあります。 Tetoriminoの実装と更新処理を追加しました。 Blockクラスがあると描画を共通化できることに気がついたので、追加しました。 wez.termで実行すると、カラーテーマの影響を受けるようです。 赤色を指定していますが、緑色で表示されます。… <p><a href="https://ledsun.hatenablog.com/entry/2023/12/30/162457">Ruby&#x3067;&#x30C6;&#x30C8;&#x30EA;&#x30B9;&#x3092;&#x52D5;&#x304B;&#x3059; &#x305D;&#x306E;1 - @ledsun blog</a> の続きです。 前回、枠を表示しました。 今回は、I字型テトリミノを表示して落下させます。</p> <p><a href="https://github.com/ledsun/tetoris/commit/9000667616f82486fe55c20e2c42b9c29e41bf34">https://github.com/ledsun/tetoris/commit/9000667616f82486fe55c20e2c42b9c29e41bf34</a> の1コミットにまとめてあります。 Tetoriminoの実装と更新処理を追加しました。 Blockクラスがあると描画を共<a class="keyword" href="https://d.hatena.ne.jp/keyword/%C4%CC%B2%BD">通化</a>できることに気がついたので、追加しました。</p> <p><a href="https://gyazo.com/80e2b730094319343e7a058a46fdc6b3"><video width="408" autoplay muted loop playsinline controls><source src="https://i.gyazo.com/80e2b730094319343e7a058a46fdc6b3.mp4" type="video/mp4"/></video></a></p> <p>wez.termで実行すると、カラーテーマの影響を受けるようです。 赤色を指定していますが、緑色で表示されます。 VisualStudio Codeのターミナルで実行すると、赤で表示されます。</p> <p><a href="https://gyazo.com/e253caa6f2d1088827fb925050713859"><video width="506" autoplay muted loop playsinline controls><source src="https://i.gyazo.com/e253caa6f2d1088827fb925050713859.mp4" type="video/mp4"/></video></a></p> ledsun Rubyでテトリスを実装する その1 hatenablog://entry/6801883189070965417 2023-12-30T16:24:57+09:00 2023-12-30T20:37:12+09:00 WSL2上にRuby開発環境を構築してテトリスを作ってみた #Ruby - Qiita を見ながらテトリスを動かそうとしています。 読む分には簡単にできそうと思いました。 実際に、写経して動かそうとしてみたら結構大変でした。 特に、Cursesアプリケーションのデバッグがとても大変です。 一気に全部動かすのは諦めます。 マイルストーンを切ることにしました。 まずは、ゲームの盤面を表示します。 ゲームの盤面を表示したスクリーンショット ここまでのソースコードです。 GitHub - ledsun/tetoris at draw_wall 次は、一種類のテトリミノが落ちてくるのを目指します。 <p><a href="https://qiita.com/flee_rife/items/c4bc3ff007a9af4f2625">WSL2&#x4E0A;&#x306B;Ruby&#x958B;&#x767A;&#x74B0;&#x5883;&#x3092;&#x69CB;&#x7BC9;&#x3057;&#x3066;&#x30C6;&#x30C8;&#x30EA;&#x30B9;&#x3092;&#x4F5C;&#x3063;&#x3066;&#x307F;&#x305F; #Ruby - Qiita</a> を見ながら<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C6%A5%C8%A5%EA%A5%B9">テトリス</a>を動かそうとしています。 読む分には簡単にできそうと思いました。 実際に、写経して動かそうとしてみたら結構大変でした。 特に、<a class="keyword" href="https://d.hatena.ne.jp/keyword/Curses">Curses</a>アプリケーションの<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C7%A5%D0%A5%C3%A5%B0">デバッグ</a>がとても大変です。</p> <p>一気に全部動かすのは諦めます。 <a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%DE%A5%A4%A5%EB%A5%B9%A5%C8%A1%BC%A5%F3">マイルストーン</a>を切ることにしました。 まずは、ゲームの盤面を表示します。</p> <p><figure class="figure-image figure-image-fotolife" title="ゲームの盤面を表示したスクリーンショット"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/l/ledsun/20231230/20231230161613.png" width="310" height="626" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>ゲームの盤面を表示した<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%EA%A1%BC%A5%F3%A5%B7%A5%E7%A5%C3%A5%C8">スクリーンショット</a></figcaption></figure></p> <p>ここまでの<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%BD%A1%BC%A5%B9%A5%B3%A1%BC%A5%C9">ソースコード</a>です。 <a href="https://github.com/ledsun/tetoris/tree/draw_wall">GitHub - ledsun/tetoris at draw_wall</a></p> <p>次は、一種類のテトリミノが落ちてくるのを目指します。</p> ledsun wez.termのテーマを変えた hatenablog://entry/6801883189070803535 2023-12-30T00:06:04+09:00 2023-12-30T00:06:04+09:00 変更後のテーマのスクリーンショット matrixという中2っぽいテーマにしました。 ついでに .wezterm.lua に次の設定を追加してフォントを変更しました。 config.font = wezterm.font '源ノ角ゴシック Code JP' config.font_size = 14.0 もともとは補完途中の文字が暗い赤で表示されて見にくかったのでテーマを変えようとしました。 途中で、wez.termのテーマではなく、fish-shellのcolor-schemeの設定だと気がつきました。 fish-shellの設定をいじっているうちに直ってしまいました。 しかし、どの設定がきい… <p><figure class="figure-image figure-image-fotolife" title="変更後のテーマのスクリーンショット"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/l/ledsun/20231229/20231229235335.png" width="633" height="198" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>変更後のテーマの<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%EA%A1%BC%A5%F3%A5%B7%A5%E7%A5%C3%A5%C8">スクリーンショット</a></figcaption></figure></p> <p><a href="https://wezfurlong.org/wezterm/colorschemes/m/index.html#matrix">matrix</a>という中2っぽいテーマにしました。</p> <p>ついでに <code>.wezterm.lua</code> に次の設定を追加してフォントを変更しました。</p> <pre class="code lang-lua" data-lang="lua" data-unlink>config.font = wezterm.font <span class="synConstant">'源ノ角ゴシック Code JP'</span> config.font_size = <span class="synConstant">14.0</span> </pre> <p>もともとは補完途中の文字が暗い赤で表示されて見にくかったのでテーマを変えようとしました。 途中で、wez.termのテーマではなく、fish-shellのcolor-<a class="keyword" href="https://d.hatena.ne.jp/keyword/scheme">scheme</a>の設定だと気がつきました。 fish-shellの設定をいじっているうちに直ってしまいました。 しかし、どの設定がきいたのかわかりません・・・。</p> <p>明確にわかっているのは、以前、<a href="https://github.com/microsoft/inshellisense">GitHub - microsoft/inshellisense: IDE style command line auto complete</a>を試したときの設定が <code>.config/fish/config.fish</code>に残っているのを消しました。 でも、inshellisenseを試す前から、色が暗い赤にだったので影響するとは思えないんですよね・・・。</p> ledsun 2023年にruby.wasmにマージできたプルリクエスト hatenablog://entry/6801883189070444881 2023-12-28T22:29:35+09:00 2023-12-28T22:29:35+09:00 https://github.com/ruby/ruby.wasm/pulls?q=+author%3Aledsun+is%3Amerged+created%3A%3E2023-01-01 で一覧できます。 6件でした。 時系列で見ていきます。 github.com JavaScriptのオブジェクトを、newメソッドで作れるようにしました。 それまでは、次のようにJavaScript側でnewメソッドを呼ぶ必要がありました。 JS.eval 'return new URLSearchParams(location.search)' 次のようにRubyの中でnewメソッドを呼べるようにしました… <p><a href="https://github.com/ruby/ruby.wasm/pulls?q=+author%3Aledsun+is%3Amerged+created%3A%3E2023-01-01">https://github.com/ruby/ruby.wasm/pulls?q=+author%3Aledsun+is%3Amerged+created%3A%3E2023-01-01</a> で一覧できます。 6件でした。 時系列で見ていきます。</p> <hr /> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Fruby%2Fruby.wasm%2Fpull%2F246" title="Create a JavaScript object with the new method. by ledsun · Pull Request #246 · ruby/ruby.wasm" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://github.com/ruby/ruby.wasm/pull/246">github.com</a></cite></p> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/JavaScript">JavaScript</a>のオブジェクトを、newメソッドで作れるようにしました。 それまでは、次のように<a class="keyword" href="https://d.hatena.ne.jp/keyword/JavaScript">JavaScript</a>側でnewメソッドを呼ぶ必要がありました。</p> <pre class="code lang-ruby" data-lang="ruby" data-unlink><span class="synType">JS</span>.eval <span class="synSpecial">'</span><span class="synConstant">return new URLSearchParams(location.search)</span><span class="synSpecial">'</span> </pre> <p>次のように<a class="keyword" href="https://d.hatena.ne.jp/keyword/Ruby">Ruby</a>の中でnewメソッドを呼べるようにしました。</p> <pre class="code lang-ruby" data-lang="ruby" data-unlink><span class="synType">JS</span>.global[<span class="synConstant">:URLSearchParams</span>].new(<span class="synType">JS</span>.global[<span class="synConstant">:location</span>][<span class="synConstant">:search</span>]) </pre> <hr /> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Fruby%2Fruby.wasm%2Fpull%2F248" title="Add JS::True and JS::False by ledsun · Pull Request #248 · ruby/ruby.wasm" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://github.com/ruby/ruby.wasm/pull/248">github.com</a></cite></p> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/JavaScript">JavaScript</a>のtrue, falseと<a class="keyword" href="https://d.hatena.ne.jp/keyword/Ruby">Ruby</a>のtrue, falseは異なるオブジェクトです。 ですので、次の比較は常にfalseになります。</p> <pre class="code lang-ruby" data-lang="ruby" data-unlink><span class="synStatement">if</span> searchParams.has(<span class="synSpecial">'</span><span class="synConstant">phrase</span><span class="synSpecial">'</span>) == <span class="synConstant">true</span> ... <span class="synStatement">end</span> </pre> <p>これを簡単に書くために、<a class="keyword" href="https://d.hatena.ne.jp/keyword/JavaScript">JavaScript</a>のtrueを定数にしました。 次のように書けます。</p> <pre class="code lang-ruby" data-lang="ruby" data-unlink><span class="synStatement">if</span> searchParams.has(<span class="synSpecial">'</span><span class="synConstant">phrase</span><span class="synSpecial">'</span>) == <span class="synType">JS</span>::<span class="synType">True</span> ... <span class="synStatement">end</span> </pre> <hr /> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Fruby%2Fruby.wasm%2Fpull%2F267" title="Use WebAssembly.compileStreaming by ledsun · Pull Request #267 · ruby/ruby.wasm" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://github.com/ruby/ruby.wasm/pull/267">github.com</a></cite></p> <p>WebAssembly.complieStreamingというwasmバイナリのダウンロードと<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%D1%A5%A4%A5%EB">コンパイル</a>を同時に実行する<a class="keyword" href="https://d.hatena.ne.jp/keyword/API">API</a>を使うようにしました。 40ms速くなりました。 ダウンロードの方が400msぐらい掛かって支配的です。 今のところはそんなに効果はないですが、将来効果が大きくなるといいですね。</p> <hr /> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Fruby%2Fruby.wasm%2Fpull%2F274" title="Update zlib version to resolve build errors. by ledsun · Pull Request #274 · ruby/ruby.wasm" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://github.com/ruby/ruby.wasm/pull/274">github.com</a></cite></p> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/ruby">ruby</a>.wasmはビルドの途中でzlibの<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%BD%A1%BC%A5%B9%A5%B3%A1%BC%A5%C9">ソースコード</a>をダウンロードします。 zlib 1.3がリリースされたときに、旧バージョンの<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%BD%A1%BC%A5%B9%A5%B3%A1%BC%A5%C9">ソースコード</a>がダウンロードできなくなりました。 <a class="keyword" href="https://d.hatena.ne.jp/keyword/ruby">ruby</a>.wasmのビルドも通らなくなったので、その修正です。</p> <hr /> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Fruby%2Fruby.wasm%2Fpull%2F292" title="Add RequireRemote#load to load external Ruby scripts from server by ledsun · Pull Request #292 · ruby/ruby.wasm" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://github.com/ruby/ruby.wasm/pull/292">github.com</a></cite></p> <p><code>Kernel#require_relative</code>をパッチするためのメソッドとして、<code>RequireRemote#load</code>を追加しました。 自作の<a class="keyword" href="https://d.hatena.ne.jp/keyword/Ruby">Ruby</a><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%EA%A5%D7%A5%C8">スクリプト</a>を読み込むだけなら次のパッチで行けます。</p> <pre class="code lang-ruby" data-lang="ruby" data-unlink><span class="synPreProc">require</span> <span class="synSpecial">'</span><span class="synConstant">js/require_remote</span><span class="synSpecial">'</span> <span class="synPreProc">module</span> <span class="synType">Kernel</span> <span class="synPreProc">def</span> <span class="synIdentifier">require_relative</span>(path) = <span class="synType">JS</span>::<span class="synType">RequireRemote</span>.instance.load(path) <span class="synPreProc">end</span> </pre> <p>これだけだと組み込みgemでつかっている<code>requirue_relative</code>が壊れます。 もうちょっと複雑なパッチが必要です。 <a href="https://github.com/ruby/ruby.wasm/blob/6acf40356079463b9b8d545031e8562a9b3931d2/packages/npm-packages/ruby-wasm-wasi/example/require_relative/index.html">ruby.wasm/packages/npm-packages/ruby-wasm-wasi/example/require_relative/index.html at 6acf40356079463b9b8d545031e8562a9b3931d2 &middot; ruby/ruby.wasm &middot; GitHub</a> を見てください。</p> <p>require_remoteを<a class="keyword" href="https://d.hatena.ne.jp/keyword/ruby">ruby</a>.wasmのbundled gemにする計画があります。 そうしたら、このパッチもgemに入れてもいいのかもしれません。 まあ、もう少し先の話なので、一先ずは<code>RequireRemote#load</code>を組み込んだ自作のアプリケーションを作ります。</p> <hr /> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Fruby%2Fruby.wasm%2Fpull%2F326" title="Modify the URL pattern to be replaced in the mock as the npm package is moved. by ledsun · Pull Request #326 · ruby/ruby.wasm" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://github.com/ruby/ruby.wasm/pull/326">github.com</a></cite></p> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/ruby">ruby</a>.wasmのnpmパッケージの名称が変わりました。 そのときのテストコードの修正漏れの対応です。</p> ledsun 相対URLの解決 hatenablog://entry/6801883189069748281 2023-12-25T22:47:33+09:00 2023-12-25T22:48:26+09:00 URL: URL() コンストラクター - Web API | MDN を使うと、基準になるURLからの相対パスを解決したURLが得られます。 例えば、次のように使います。 // ベース URL: let baseUrl = "https://developer.mozilla.org"; new URL("ja/docs", baseUrl); // => 'https://developer.mozilla.org/ja/docs' このコンストラクターの挙動を試しているときに、次の例を考えました。 new URL('a.rb', 'http://exapmle.com/lib').toS… <p><a href="https://developer.mozilla.org/ja/docs/Web/API/URL/URL">URL: URL() &#x30B3;&#x30F3;&#x30B9;&#x30C8;&#x30E9;&#x30AF;&#x30BF;&#x30FC; - Web API | MDN</a> を使うと、基準になるURLからの<a class="keyword" href="https://d.hatena.ne.jp/keyword/%C1%EA%C2%D0%A5%D1%A5%B9">相対パス</a>を解決したURLが得られます。 例えば、次のように使います。</p> <pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synComment">// ベース URL:</span> <span class="synIdentifier">let</span> baseUrl = <span class="synConstant">&quot;https://developer.mozilla.org&quot;</span>; <span class="synStatement">new</span> URL(<span class="synConstant">&quot;ja/docs&quot;</span>, baseUrl); <span class="synComment">// =&gt; 'https://developer.mozilla.org/ja/docs'</span> </pre> <p>この<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%B9%A5%C8%A5%E9%A5%AF%A5%BF%A1%BC">コンストラクター</a>の挙動を試しているときに、次の例を考えました。</p> <pre class="code lang-javascript" data-lang="javascript" data-unlink><span class="synStatement">new</span> URL(<span class="synConstant">'a.rb'</span>, <span class="synConstant">'http://exapmle.com/lib'</span>).toString() <span class="synComment">// =&gt; 'http://exapmle.com/a.rb'</span> </pre> <p>このとき <code>http://exapmle.com/lib/a.rb</code> となって、<code>lib</code><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リの中を参照して欲しいのではないでしょうか?</p> <p>これは<a class="keyword" href="https://d.hatena.ne.jp/keyword/JavaScript">JavaScript</a>特有の動作なのでしょうか?</p> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/Ruby">Ruby</a>でも試してみました。 <a class="keyword" href="https://d.hatena.ne.jp/keyword/Ruby">Ruby</a>ではURLの結合には <a href="https://docs.ruby-lang.org/ja/latest/method/URI/s/join.html">URI.join</a> を使います。</p> <pre class="code lang-ruby" data-lang="ruby" data-unlink><span class="synPreProc">require</span> <span class="synSpecial">'</span><span class="synConstant">uri</span><span class="synSpecial">'</span> <span class="synType">URI</span>.join(<span class="synType">URI</span>.parse(<span class="synSpecial">'</span><span class="synConstant">http://exapmle.com/lib</span><span class="synSpecial">'</span>), <span class="synSpecial">'</span><span class="synConstant">a.rb</span><span class="synSpecial">'</span>) <span class="synComment"># =&gt; #&lt;URI::HTTP http://exapmle.com/a.rb&gt;</span> </pre> <p>やはり <code>lib</code> が消えます。 統一された動作です。 もしかしてこれはどこかで決まっているのでしょうか?</p> <p>るりまに以下の説明がありました。</p> <blockquote><p>[RFC2396] の Section 5.2 の仕様に従って連結します。</p></blockquote> <p>というわけで<a class="keyword" href="https://d.hatena.ne.jp/keyword/RFC">RFC</a>を見てます。 <a href="https://datatracker.ietf.org/doc/html/rfc2396#autoid-33">https://datatracker.ietf.org/doc/html/rfc2396#autoid-33</a></p> <blockquote><p> 6) If this step is reached, then we are resolving a relative-path reference. The relative path needs to be merged with the base <a class="keyword" href="https://d.hatena.ne.jp/keyword/URI">URI</a>'s path. Although there are many ways to do this, we will describe a simple method using a separate string buffer.</p> <pre><code> a) All but the last segment of the base URI's path component is copied to the buffer. In other words, any characters after the last (right-most) slash character, if any, are excluded. </code></pre></blockquote> <p>base <a class="keyword" href="https://d.hatena.ne.jp/keyword/URI">URI</a>の最後のスラッシュ以降に何かあれば、その部分はバッファ(解決後のURLを結合するための場所)に入れないそうです。 なるほど、<a class="keyword" href="https://d.hatena.ne.jp/keyword/JavaScript">JavaScript</a>も<a class="keyword" href="https://d.hatena.ne.jp/keyword/Ruby">Ruby</a>もこの動きをしていそうです。</p> <p>この記事を書いている途中で気がつきました。 URLは文字列で、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D5%A5%A1%A5%A4%A5%EB%A5%B7%A5%B9%A5%C6%A5%E0">ファイルシステム</a>ではありません。 <code>http://exapmle.com/lib</code> がファイルか<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リかという区別は、文字列から読み取るしかありません。 すると</p> <ul> <li><code>/</code>で終わるのが、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リ</li> <li><code>/</code>の後ろに文字列が続いていたらファイル</li> </ul> <p>みたいな、単純な方法で区別するしかない気がしてきました。 <code>lib</code>が<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リに見えるのは、背景知識があるから人間に判別できているっぽいです。</p> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/Ruby">Ruby</a>と<a class="keyword" href="https://d.hatena.ne.jp/keyword/JavaScript">JavaScript</a>、ファイルとURLの間を行ったり来たりしていると、自分がどこにいるのか、よく見失います。</p> ledsun Playwrightでリダイレクト後のHTTPリクエストをMockできない hatenablog://entry/6801883189069132717 2023-12-23T18:03:16+09:00 2023-12-23T18:04:38+09:00 ruby.wasmのテストコードを書いていました。 PlaywrightでHTTPリクエストをMockしているスクリーンショット cdn.jsdriver.netへのリクエストをMockして、レスポンスの内容をローカルファイルに置き換えています。 リダイレクトしたあとはMockできていないスクリーンショット 前述のスクリーンショットと、同じURLに対するリクエストですが、302レスポンスでリダイレクトしたあとはMockできません。 調べてたら、次のコメントを発見しました。 tests for request event and interception with redirects by tj… <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/ruby">ruby</a>.wasmのテストコードを書いていました。</p> <p><figure class="figure-image figure-image-fotolife" title="PlaywrightでHTTPリクエストをMockしているスクリーンショット"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/l/ledsun/20231223/20231223175324.png" width="1154" height="57" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>PlaywrightでHTTPリク<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A8%A5%B9">エス</a>トをMockしている<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%EA%A1%BC%A5%F3%A5%B7%A5%E7%A5%C3%A5%C8">スクリーンショット</a></figcaption></figure></p> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/cdn">cdn</a>.jsdriver.netへのリク<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A8%A5%B9">エス</a>トをMockして、レスポンスの内容をローカルファイルに置き換えています。</p> <p><figure class="figure-image figure-image-fotolife" title="リダイレクトしたあとはMockできていないスクリーンショット"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/l/ledsun/20231223/20231223175538.png" width="1059" height="122" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span><figcaption>リダイレクトしたあとはMockできていない<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%EA%A1%BC%A5%F3%A5%B7%A5%E7%A5%C3%A5%C8">スクリーンショット</a></figcaption></figure></p> <p>前述の<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%EA%A1%BC%A5%F3%A5%B7%A5%E7%A5%C3%A5%C8">スクリーンショット</a>と、同じURLに対するリク<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A8%A5%B9">エス</a>トですが、302レスポンスでリダイレクトしたあとはMockできません。</p> <p>調べてたら、次のコメントを発見しました。</p> <p><a href="https://github.com/microsoft/playwright/pull/3994#issuecomment-700980039">tests for request event and interception with redirects by tjenkinson &middot; Pull Request #3994 &middot; microsoft/playwright &middot; GitHub</a></p> <blockquote><p><a class="keyword" href="https://d.hatena.ne.jp/keyword/The%20network">The network</a> interception in Playwright is implemented on the Browser -> Network stack boundary. Once the request is in <a class="keyword" href="https://d.hatena.ne.jp/keyword/the%20network">the network</a> stack, it is going to handle the redirects and report them, but not allow intercepting them.</p></blockquote> <p>Playwrightが割り込んでいるのは、ブラウザとネットワークスタックの間だそうです。</p> <p>fetchメソッドはデフォルトで、リダイレクトレスポンスを自動的に追いかけます。 なるほど!この動きはネットワークスタックに含まれていそうです。</p> ledsun