diff options
Diffstat (limited to 'public/posts/2022-09-29')
| -rw-r--r-- | public/posts/2022-09-29/write-fizzbuzz-in-php-2-letters-per-line/index.html | 222 |
1 files changed, 111 insertions, 111 deletions
diff --git a/public/posts/2022-09-29/write-fizzbuzz-in-php-2-letters-per-line/index.html b/public/posts/2022-09-29/write-fizzbuzz-in-php-2-letters-per-line/index.html index 3d96761..42931a5 100644 --- a/public/posts/2022-09-29/write-fizzbuzz-in-php-2-letters-per-line/index.html +++ b/public/posts/2022-09-29/write-fizzbuzz-in-php-2-letters-per-line/index.html @@ -57,69 +57,69 @@ </ol> </section> <section id="section--_記事の構成について"> - <h2><a href="#section--_記事の構成について">記事の構成について</a></h2> + <h2><a href="#section--_記事の構成について">記事の構成について</a></h2> <p> - この記事は、普通の fizzbuzz を徐々に変形して最終形にしていく、という構成で書かれている。最終形を見てどのような仕組みで動いているのか解読してから解説を読みたい、というかたがいれば、<a href="https://gist.github.com/nsfisis/04c227d5a419867472a0b23a83ad2919#file-fizzbuzz-php-2-letters-per-line-and-supports-php-8-x-without-warnings">このページ</a>にソースコードがあるので、そちらを先に見てほしい。 + この記事は、普通の fizzbuzz を徐々に変形して最終形にしていく、という構成で書かれている。最終形を見てどのような仕組みで動いているのか解読してから解説を読みたい、というかたがいれば、 <a href="https://gist.github.com/nsfisis/04c227d5a419867472a0b23a83ad2919#file-fizzbuzz-php-2-letters-per-line-and-supports-php-8-x-without-warnings">このページ</a> にソースコードがあるので、そちらを先に見てほしい。 </p> </section> - + <section id="section--_レギュレーション"> - <h2><a href="#section--_レギュレーション">レギュレーション</a></h2> + <h2><a href="#section--_レギュレーション">レギュレーション</a></h2> <p> PHP で、次のような制約の下に fizzbuzz を書いた。 </p> - + <ul> <li> - 1行あたりの文字数は2文字までに収めること (ただし<code><?php</code>タグは除く) + 1行あたりの文字数は2文字までに収めること (ただし <code><?php</code> タグは除く) <ul> <li> - 厳密な定義:<code><?php</code>タグ以降のソースコードが、2 byte ごとに ラインフィード (LF) で区切られること + 厳密な定義: <code><?php</code> タグ以降のソースコードが、2 byte ごとに ラインフィード (LF) で区切られること </li> </ul> </li> - + <li> スペースやタブを使用しないこと </li> - + <li> - ループのアンロールをしないこと + ループのアンロールをしないこと <ul> <li> 100 回ループの代わりに 100 回コードをコピペ、というのは禁止 </li> </ul> </li> - + <li> PHP 7.4〜8.1 で動作すること </li> - + <li> 実行時に Notice や Warning が出ないこと </li> - + <li> 標準的なインストール構成の PHP で実現できること (デフォルトで有効になっていない拡張等を使わないこと) </li> </ul> - + <p> - 備考: PHP には<code>short_open_tag</code>というオプションがあり、これを有効にするとファイル冒頭の<code><?php</code>の代わりに<code><?</code>を使うことができ、文字どおり1行2文字で書ける。ただ、このオプションはデフォルト off になっている環境が多いようなので、今回は使わないことにした。 + 備考: PHP には <code>short_open_tag</code> というオプションがあり、これを有効にするとファイル冒頭の <code><?php</code> の代わりに <code><?</code> を使うことができ、文字どおり1行2文字で書ける。ただ、このオプションはデフォルト off になっている環境が多いようなので、今回は使わないことにした。 </p> </section> - + <section id="section--_主な障害"> - <h2><a href="#section--_主な障害">主な障害</a></h2> + <h2><a href="#section--_主な障害">主な障害</a></h2> <p> 1行あたりの文字数など、適当に改行を挟めばいいだけではないのか? </p> - + <p> 特に、C言語でこのような試みをおこなったことがあるかたならそう思うだろう。事実、Cでのこの制約はほとんど無意味に等しい。 </p> - + <pre class="highlight" language="c" linenumbering="unnumbered"><code class="highlight">#\ i\ n\ @@ -194,96 +194,96 @@ c\ ); <span class="hljs-comment">/* あとは同じように普通のプログラムを変形するだけなので省略 */</span></code></pre> - + <p> バックスラッシュを使った行継続がトークンを区切らない、というのがポイントだ。 </p> - + <p> - さて、PHP ではそもそもバックスラッシュを行継続に使うことができない。これにより、「3文字以上からなるトークンが一切使えない」という制約が課される。例えば、<code>echo</code>で出力することや、<code>for</code>でループすること、<code>new</code>でインスタンスを生成することができない。特に、出力は fizzbuzz をどんなアルゴリズムで実装しようとおこなわなければならないので、できないのは致命的である。 + さて、PHP ではそもそもバックスラッシュを行継続に使うことができない。これにより、「3文字以上からなるトークンが一切使えない」という制約が課される。例えば、<code>echo</code> で出力することや、<code>for</code> でループすること、<code>new</code> でインスタンスを生成することができない。特に、出力は fizzbuzz をどんなアルゴリズムで実装しようとおこなわなければならないので、できないのは致命的である。 </p> - + <p> 当然、名前が3文字以上ある関数も使えない。なお、標準 PHP の範囲内において、名前が 2文字以下の関数は以下のとおりである: </p> - + <ul> <li> - <code>_</code>:<code>gettext</code>のエイリアス + <code>_</code>: <code>gettext</code> のエイリアス </li> - + <li> - <code>dl</code>: 拡張モジュールをロードする + <code>dl</code>: 拡張モジュールをロードする </li> - + <li> - <code>pi</code>: 円周率を返す + <code>pi</code>: 円周率を返す </li> </ul> - + <p> (環境によって多少は変わるかも) </p> - + <p> - 2文字の関数を定義しまくった拡張モジュールを用意しておいて<code>dl()</code>で読み込む行為は、レギュレーションで定めた + 2文字の関数を定義しまくった拡張モジュールを用意しておいて <code>dl()</code> で読み込む行為は、レギュレーションで定めた </p> - + <blockquote> <ul> <li> - 標準的なインストール構成の PHP で実現できること (デフォルトで有効になっていない拡張等を使わないこと) + 標準的なインストール構成の PHP で実現できること (デフォルトで有効になっていない拡張等を使わないこと) </li> </ul> </blockquote> - + <p> に反する (というより、「それだとおもしろくもなんともないので、このルールを足した」というのが正しい)。 </p> - + <p> - また、2文字だと文字列がまともに書けないのも辛い。<code>''</code>だけで 2文字使うので、「1文字の文字列リテラル」というものを書くことができない。PHP では文字列リテラル中に生の改行が書けるので + また、2文字だと文字列がまともに書けないのも辛い。<code>''</code> だけで 2文字使うので、「1文字の文字列リテラル」というものを書くことができない。PHP では文字列リテラル中に生の改行が書けるので </p> - + <pre class="highlight" language="php" linenumbering="unnumbered"><code class="highlight"><span class="hljs-variable">$a</span> =<span class="hljs-string">' a'</span> ;;</code></pre> - + <p> - とすると<code>$a</code>は<code>"\na"</code>になるのだが、余計な改行が入ってしまう。 + とすると <code>$a</code> は <code>"\na"</code> になるのだが、余計な改行が入ってしまう。 </p> - + <p> これらの障害をどのように乗り越えるのか、次節から見ていく。 </p> </section> - + <section id="section--_解説"> - <h2><a href="#section--_解説">解説</a></h2> + <h2><a href="#section--_解説">解説</a></h2> <section id="section--_普通の_fizzbuzz"> - <h3><a href="#section--_普通の_fizzbuzz">普通の (?) fizzbuzz</a></h3> + <h3><a href="#section--_普通の_fizzbuzz">普通の (?) fizzbuzz</a></h3> <p> まずは普通に書くとしよう。 </p> - + <pre class="highlight" language="php" linenumbering="unnumbered"><code class="highlight">&lt;?php <span class="hljs-keyword">for</span> (<span class="hljs-variable">$i</span> = <span class="hljs-number">1</span>; <span class="hljs-variable">$i</span> &lt; <span class="hljs-number">100</span>; <span class="hljs-variable">$i</span>++) { <span class="hljs-keyword">echo</span> ((<span class="hljs-variable">$i</span> % <span class="hljs-number">3</span> ? <span class="hljs-string">''</span> : <span class="hljs-string">'Fizz'</span>) . (<span class="hljs-variable">$i</span> % <span class="hljs-number">5</span> ? <span class="hljs-string">''</span> : <span class="hljs-string">'Buzz'</span>) ?: <span class="hljs-variable">$i</span>) . <span class="hljs-string">"\n"</span>; }</code></pre> - + <p> 素直に書いた fizzbuzz とは言い難いが、このくらいは普通だということにしておかないと、この先がやっていられないので許してほしい。 </p> </section> - + <section id="section--_for_の排除"> - <h3><a href="#section--_for_の排除"><code>for</code>の排除</a></h3> + <h3><a href="#section--_for_の排除"><code>for</code> の排除</a></h3> <p> - <code>for</code>は、3文字もある長いキーワードである。こんなものは使えない。<code>array_</code>系の関数を使って、適当に置き換えるとしよう。 + <code>for</code> は、3文字もある長いキーワードである。こんなものは使えない。<code>array_</code> 系の関数を使って、適当に置き換えるとしよう。 </p> - + <pre class="highlight" language="php" linenumbering="unnumbered"><code class="highlight">&lt;?php <span class="hljs-variable">$s</span> = <span class="hljs-title function_ invoke__">range</span>(<span class="hljs-number">1</span>, <span class="hljs-number">100</span>); @@ -292,18 +292,18 @@ a'</span> fn(<span class="hljs-variable">$i</span>) =&gt; <span class="hljs-title function_ invoke__">printf</span>(((<span class="hljs-variable">$i</span> % <span class="hljs-number">3</span> ? <span class="hljs-string">''</span> : <span class="hljs-string">'Fizz'</span>) . (<span class="hljs-variable">$i</span> % <span class="hljs-number">5</span> ? <span class="hljs-string">''</span> : <span class="hljs-string">'Buzz'</span>) ?: <span class="hljs-variable">$i</span>) . <span class="hljs-string">"\n"</span>), );</code></pre> - + <p> - <code>array_walk</code>や<code>range</code>、<code>printf</code>といった<code>for</code>よりも長いトークンが現れてしまったが、これは次節で直すことにする。なお、<code>echo</code>は文 (statement) であり式 (expression) ではないので、式である<code>printf</code>に置き換えた。 + <code>array_walk</code> や <code>range</code>、<code>printf</code> といった <code>for</code> よりも長いトークンが現れてしまったが、これは次節で直すことにする。なお、<code>echo</code> は文 (statement) であり式 (expression) ではないので、式である <code>printf</code> に置き換えた。 </p> </section> - + <section id="section--_関数呼び出しの短縮"> - <h3><a href="#section--_関数呼び出しの短縮">関数呼び出しの短縮</a></h3> + <h3><a href="#section--_関数呼び出しの短縮">関数呼び出しの短縮</a></h3> <p> - <code>range</code>、<code>array_walk</code>、<code>printf</code>は長すぎるのでどうにかせねばならない。ここで、PHP の可変関数を使う。可変関数とは、関数名が文字列として入った変数を経由して、関数を呼び出す機能である。 + <code>range</code>、<code>array_walk</code>、<code>printf</code> は長すぎるのでどうにかせねばならない。ここで、PHP の可変関数を使う。可変関数とは、関数名が文字列として入った変数を経由して、関数を呼び出す機能である。 </p> - + <pre class="highlight" language="php" linenumbering="unnumbered"><code class="highlight">&lt;?php <span class="hljs-variable">$r</span> = <span class="hljs-string">'range'</span>; @@ -316,18 +316,18 @@ a'</span> <span class="hljs-function"><span class="hljs-keyword">fn</span>(<span class="hljs-params"><span class="hljs-variable">$i</span></span>) =&<span class="hljs-title">gt</span></span>; <span class="hljs-variable">$p</span>(((<span class="hljs-variable">$i</span> % <span class="hljs-number">3</span> ? <span class="hljs-string">''</span> : <span class="hljs-string">'Fizz'</span>) . (<span class="hljs-variable">$i</span> % <span class="hljs-number">5</span> ? <span class="hljs-string">''</span> : <span class="hljs-string">'Buzz'</span>) ?: <span class="hljs-variable">$i</span>) . <span class="hljs-string">"\n"</span>), );</code></pre> - + <p> - これで関数を呼び出している所は短くなった。では、<code>$r</code>や<code>$w</code>や<code>$p</code>、また<code>'Fizz'</code>や<code>'Buzz'</code>はどうやって 1行2文字に収めるのか。次のテクニックへ移ろう。 + これで関数を呼び出している所は短くなった。では、<code>$r</code> や <code>$w</code> や <code>$p</code>、また <code>'Fizz'</code> や <code>'Buzz'</code> はどうやって 1行2文字に収めるのか。次のテクニックへ移ろう。 </p> </section> - + <section id="section--_余談_php_8_x_で動作しなくてもいいなら"> - <h3><a href="#section--_余談_php_8_x_で動作しなくてもいいなら">余談: PHP 8.x で動作しなくてもいいなら</a></h3> + <h3><a href="#section--_余談_php_8_x_で動作しなくてもいいなら">余談: PHP 8.x で動作しなくてもいいなら</a></h3> <p> 今回使ったテクニックを説明する前に、余談として、文字列リテラルの短縮法として今回採用しなかったものを紹介する。 </p> - + <blockquote> <ul> <li> @@ -335,22 +335,22 @@ a'</span> </li> </ul> </blockquote> - + <p> - というルールがない場合、「未定義の定数が評価された場合、その定数の名前が値になる」という PHP 7.x までの仕様が利用できる。例えば、<code>Fizz</code>という文字列が欲しければ、次のようにする。 + というルールがない場合、「未定義の定数が評価された場合、その定数の名前が値になる」という PHP 7.x までの仕様が利用できる。例えば、 <code>Fizz</code> という文字列が欲しければ、次のようにする。 </p> - + <pre class="highlight" language="php" linenumbering="unnumbered"><code class="highlight"><span class="hljs-variable">$f</span> =F .i .z .z ;;</code></pre> - + <p> - こうして簡単に文字列を作れる。なお、この仕様は 7.x 時点でも警告を受けるので、<code>@</code>演算子を使って抑制してやるとよい。 + こうして簡単に文字列を作れる。なお、この仕様は 7.x 時点でも警告を受けるので、<code>@</code> 演算子を使って抑制してやるとよい。 </p> - + <pre class="highlight" language="php" linenumbering="unnumbered"><code class="highlight"><span class="hljs-variable">$f</span> =@ F. @@ -360,22 +360,22 @@ F. .<span class="hljs-comment">#</span> @z ;;</code></pre> - + <p> むしろ、このことがわかっていたからこそ PHP 8.x での動作を要件に課したところがある。 </p> </section> - + <section id="section--_文字列リテラルの短縮"> - <h3><a href="#section--_文字列リテラルの短縮">文字列リテラルの短縮</a></h3> + <h3><a href="#section--_文字列リテラルの短縮">文字列リテラルの短縮</a></h3> <p> 実際に使った手法の説明に移る。 </p> - + <p> ずばり、文字列同士のビット演算を使う。PHP では、文字列同士でビット演算 (<code>&</code>、<code>|</code>、<code>^</code>) をした場合、文字列の各バイトごとに指定したビット演算がなされ、それを結合したものが演算結果となる。 </p> - + <pre class="highlight" language="php" linenumbering="unnumbered"><code class="highlight"><span class="hljs-variable">$a</span> = <span class="hljs-string">"12345"</span>; <span class="hljs-variable">$b</span> = <span class="hljs-string">"world"</span>; @@ -387,20 +387,20 @@ F. <span class="hljs-keyword">echo</span> <span class="hljs-variable">$result</span>; <span class="hljs-comment">// =&gt; F]AXQ</span></code></pre> - + <p> これを踏まえ、次のコードを見てみよう。 </p> - + <pre class="highlight" language="php" linenumbering="unnumbered"><code class="highlight"><span class="hljs-variable">$x</span> = <span class="hljs-string">"x\nOm\n"</span>; <span class="hljs-variable">$y</span> = <span class="hljs-string">"\nk!\no"</span>; <span class="hljs-variable">$r</span> = <span class="hljs-variable">$x</span> ^ <span class="hljs-variable">$y</span>; <span class="hljs-keyword">echo</span> <span class="hljs-string">"<span class="hljs-subst">$r</span>\n"</span>;</code></pre> - + <p> - 実行すると、<code>range</code>が表示される。さて、PHP では文字列リテラル中に生の改行を直接書いてもよいのだった (「主な障害」の節を参照のこと)。書きかえてみよう。 + 実行すると、<code>range</code> が表示される。さて、PHP では文字列リテラル中に生の改行を直接書いてもよいのだった (「主な障害」の節を参照のこと)。書きかえてみよう。 </p> - + <pre class="highlight" language="php" linenumbering="unnumbered"><code class="highlight"><span class="hljs-variable">$x</span> =<span class="hljs-string">'x Om @@ -413,11 +413,11 @@ o'</span> <span class="hljs-variable">$r</span> = <span class="hljs-variable">$x</span> ^ <span class="hljs-variable">$y</span>; <span class="hljs-keyword">echo</span> <span class="hljs-string">"<span class="hljs-subst">$r</span>\n"</span>;</code></pre> - + <p> - さらに<code>#</code>を使って適当に調整すると、次のようになる。 + さらに <code>#</code> を使って適当に調整すると、次のようになる。 </p> - + <pre class="highlight" language="php" linenumbering="unnumbered"><code class="highlight"><span class="hljs-variable">$x</span> =<span class="hljs-comment">#</span> <span class="hljs-string">'x @@ -436,23 +436,23 @@ o'</span> ;<span class="hljs-comment">#</span> <span class="hljs-keyword">echo</span> <span class="hljs-string">"<span class="hljs-subst">$r</span>\n"</span>;</code></pre> - + <p> - 1行あたり2文字で、<code>range</code>という文字列を生成することに成功した。他の必要な文字列にも、同様の処理をほどこす。 + 1行あたり2文字で、<code>range</code> という文字列を生成することに成功した。他の必要な文字列にも、同様の処理をほどこす。 </p> - + <p> - 備考:<code>Buzz</code>中にある小文字の<code>u</code>は、このロジックだと non-printable な文字になってしまう。ここまでのテクニックを駆使すれば回避するのはそう難しくないので、考えてみてほしい。 + 備考: <code>Buzz</code> 中にある小文字の <code>u</code> は、このロジックだと non-printable な文字になってしまう。ここまでのテクニックを駆使すれば回避するのはそう難しくないので、考えてみてほしい。 </p> </section> </section> - + <section id="section--_完成系"> - <h2><a href="#section--_完成系">完成系</a></h2> + <h2><a href="#section--_完成系">完成系</a></h2> <p> 完成したものがこちら。 </p> - + <pre class="highlight" language="php" linenumbering="unnumbered"><code class="highlight">&lt;?php <span class="hljs-variable">$x</span> @@ -603,24 +603,24 @@ _! '</span>) );</code></pre> </section> - + <section id="section--_感想など"> - <h2><a href="#section--_感想など">感想など</a></h2> + <h2><a href="#section--_感想など">感想など</a></h2> <p> PHP は、スクリプト言語の中だとシンタックスシュガーが少ない (体感)。この挑戦は不可能に思われたが、PHP マニュアルとにらめっこしていたらなんとかなった。 </p> - + <p> みんなもプログラムを細長くしよう。 </p> </section> - + <section id="section--_余談2_別解"> - <h2><a href="#section--_余談2_別解">余談2: 別解</a></h2> + <h2><a href="#section--_余談2_別解">余談2: 別解</a></h2> <p> - PHP では、バッククォートを使ってシェルを呼び出せる。これは<code>shell_exec</code>関数と等価である。さて、PHP ではバックスラッシュによる行継続が使えないと書いたが、シェルでは使える (当然だが、呼び出されるシェルに依存する。Bash なら大丈夫だろう。知らんけど)。 + PHP では、バッククォートを使ってシェルを呼び出せる。これは <code>shell_exec</code> 関数と等価である。さて、PHP ではバックスラッシュによる行継続が使えないと書いたが、シェルでは使える (当然だが、呼び出されるシェルに依存する。Bash なら大丈夫だろう。知らんけど)。 </p> - + <pre class="highlight" language="php" linenumbering="unnumbered"><code class="highlight">&lt;?php <span class="hljs-title function_ invoke__">printf</span>(` @@ -633,15 +633,15 @@ o\ <span class="hljs-number">2</span>\ <span class="hljs-number">3</span>\ `);</code></pre> - + <p> - なお、ここでは簡単のため出力に<code>printf</code>をそのまま使っているが、実際には<code>printf</code>という文字列を合成して可変関数で呼び出す。 + なお、ここでは簡単のため出力に <code>printf</code> をそのまま使っているが、実際には <code>printf</code> という文字列を合成して可変関数で呼び出す。 </p> - + <p> ただし、これでは </p> - + <blockquote> <ul> <li> @@ -649,15 +649,15 @@ o\ </li> </ul> </blockquote> - + <p> に違反してしまう。スペースが使えないと引数とコマンドを区切れない。これは困った。 </p> - + <p> もうこれ以上は不可能だと思っていたのだが、この記事の執筆中に解決する方法を思いついたので載せておく。 </p> - + <pre class="highlight" language="php" linenumbering="unnumbered"><code class="highlight">&lt;?php <span class="hljs-variable">$c</span> = <span class="hljs-string">'chr'</span>; @@ -687,19 +687,19 @@ ${ <span class="hljs-number">2</span>\ <span class="hljs-number">3</span>\ `);</code></pre> - + <p> - 先程と同じく、<code>chr</code>や<code>printf</code>を生成する部分は長くなるので省いた。 + 先程と同じく、<code>chr</code> や <code>printf</code> を生成する部分は長くなるので省いた。 </p> - + <pre class="highlight monospaced"><code>${ '_ '}</code></pre> - + <p> は変数で、中にはスペースとエスケープが入っている (<code>chr(32) . chr(92)</code>)。シェルに渡されている文字列は次のようになる。 </p> - + <pre class="highlight monospaced"><code>e\ c\ h\ @@ -708,23 +708,23 @@ o\ 1\ 2\ 3\</code></pre> - + <p> これは、前掲したコマンドと同じだ。かくして、スペースを陽に書かずにシェルをおおよそ自由に扱えるようになった。Fizzbuzz のワンライナーくらいすぐ書けるだろうから、あとはなんとかなるだろう (試してないけど)。 </p> - + <p> ということでこれは別解ということにしておく。 </p> - + <p> ちなみに、PHP 8.2 からは、この記法で Warning が出るようになるようだ。 </p> - + <pre class="highlight monospaced"><code>${ '_ '}</code></pre> - + <p> 最新版で警告が出るというのも美しくないので、私としては本編の解法を推す。 </p> |
