From 7f15e0b8277ac8b101b4f71ce57c1c5442927141 Mon Sep 17 00:00:00 2001 From: nsfisis Date: Sat, 18 Mar 2023 19:51:06 +0900 Subject: fix(nuldoc): fix whitespaces being trimmed --- .../index.html | 222 ++++++++++----------- 1 file changed, 111 insertions(+), 111 deletions(-) (limited to 'public/posts/2022-09-29/write-fizzbuzz-in-php-2-letters-per-line/index.html') 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 @@
-

記事の構成について

+

記事の構成について

- この記事は、普通の fizzbuzz を徐々に変形して最終形にしていく、という構成で書かれている。最終形を見てどのような仕組みで動いているのか解読してから解説を読みたい、というかたがいれば、このページにソースコードがあるので、そちらを先に見てほしい。 + この記事は、普通の fizzbuzz を徐々に変形して最終形にしていく、という構成で書かれている。最終形を見てどのような仕組みで動いているのか解読してから解説を読みたい、というかたがいれば、 このページ にソースコードがあるので、そちらを先に見てほしい。

- +
-

レギュレーション

+

レギュレーション

PHP で、次のような制約の下に fizzbuzz を書いた。

- + - +

- 備考: PHP にはshort_open_tagというオプションがあり、これを有効にするとファイル冒頭の<?phpの代わりに<?を使うことができ、文字どおり1行2文字で書ける。ただ、このオプションはデフォルト off になっている環境が多いようなので、今回は使わないことにした。 + 備考: PHP には short_open_tag というオプションがあり、これを有効にするとファイル冒頭の <?php の代わりに <? を使うことができ、文字どおり1行2文字で書ける。ただ、このオプションはデフォルト off になっている環境が多いようなので、今回は使わないことにした。

- +
-

主な障害

+

主な障害

1行あたりの文字数など、適当に改行を挟めばいいだけではないのか?

- +

特に、C言語でこのような試みをおこなったことがあるかたならそう思うだろう。事実、Cでのこの制約はほとんど無意味に等しい。

- +
#\
 i\
 n\
@@ -194,96 +194,96 @@ c\
 );
 
 /* あとは同じように普通のプログラムを変形するだけなので省略 */
- +

バックスラッシュを使った行継続がトークンを区切らない、というのがポイントだ。

- +

- さて、PHP ではそもそもバックスラッシュを行継続に使うことができない。これにより、「3文字以上からなるトークンが一切使えない」という制約が課される。例えば、echoで出力することや、forでループすること、newでインスタンスを生成することができない。特に、出力は fizzbuzz をどんなアルゴリズムで実装しようとおこなわなければならないので、できないのは致命的である。 + さて、PHP ではそもそもバックスラッシュを行継続に使うことができない。これにより、「3文字以上からなるトークンが一切使えない」という制約が課される。例えば、echo で出力することや、for でループすること、new でインスタンスを生成することができない。特に、出力は fizzbuzz をどんなアルゴリズムで実装しようとおこなわなければならないので、できないのは致命的である。

- +

当然、名前が3文字以上ある関数も使えない。なお、標準 PHP の範囲内において、名前が 2文字以下の関数は以下のとおりである:

- + - +

(環境によって多少は変わるかも)

- +

- 2文字の関数を定義しまくった拡張モジュールを用意しておいてdl()で読み込む行為は、レギュレーションで定めた + 2文字の関数を定義しまくった拡張モジュールを用意しておいて dl() で読み込む行為は、レギュレーションで定めた

- +
  • - 標準的なインストール構成の PHP で実現できること (デフォルトで有効になっていない拡張等を使わないこと) + 標準的なインストール構成の PHP で実現できること (デフォルトで有効になっていない拡張等を使わないこと)
- +

に反する (というより、「それだとおもしろくもなんともないので、このルールを足した」というのが正しい)。

- +

- また、2文字だと文字列がまともに書けないのも辛い。''だけで 2文字使うので、「1文字の文字列リテラル」というものを書くことができない。PHP では文字列リテラル中に生の改行が書けるので + また、2文字だと文字列がまともに書けないのも辛い。'' だけで 2文字使うので、「1文字の文字列リテラル」というものを書くことができない。PHP では文字列リテラル中に生の改行が書けるので

- +
$a
 ='
 a'
 ;;
- +

- とすると$a"\na"になるのだが、余計な改行が入ってしまう。 + とすると $a"\na" になるのだが、余計な改行が入ってしまう。

- +

これらの障害をどのように乗り越えるのか、次節から見ていく。

- +
-

解説

+

解説

-

普通の (?) fizzbuzz

+

普通の (?) fizzbuzz

まずは普通に書くとしよう。

- +
&lt;?php
 
 for ($i = 1; $i &lt; 100; $i++) {
   echo (($i % 3 ? '' : 'Fizz') . ($i % 5 ? '' : 'Buzz') ?: $i) . "\n";
 }
- +

素直に書いた fizzbuzz とは言い難いが、このくらいは普通だということにしておかないと、この先がやっていられないので許してほしい。

- +
-

forの排除

+

for の排除

- forは、3文字もある長いキーワードである。こんなものは使えない。array_系の関数を使って、適当に置き換えるとしよう。 + for は、3文字もある長いキーワードである。こんなものは使えない。array_ 系の関数を使って、適当に置き換えるとしよう。

- +
&lt;?php
 
 $s = range(1, 100);
@@ -292,18 +292,18 @@ a'
   fn($i) =&gt;
     printf((($i % 3 ? '' : 'Fizz') . ($i % 5 ? '' : 'Buzz') ?: $i) . "\n"),
 );
- +

- array_walkrangeprintfといったforよりも長いトークンが現れてしまったが、これは次節で直すことにする。なお、echoは文 (statement) であり式 (expression) ではないので、式であるprintfに置き換えた。 + array_walkrangeprintf といった for よりも長いトークンが現れてしまったが、これは次節で直すことにする。なお、echo は文 (statement) であり式 (expression) ではないので、式である printf に置き換えた。

- +
-

関数呼び出しの短縮

+

関数呼び出しの短縮

- rangearray_walkprintfは長すぎるのでどうにかせねばならない。ここで、PHP の可変関数を使う。可変関数とは、関数名が文字列として入った変数を経由して、関数を呼び出す機能である。 + rangearray_walkprintf は長すぎるのでどうにかせねばならない。ここで、PHP の可変関数を使う。可変関数とは、関数名が文字列として入った変数を経由して、関数を呼び出す機能である。

- +
&lt;?php
 
 $r = 'range';
@@ -316,18 +316,18 @@ a'
   fn($i) =&gt;
     $p((($i % 3 ? '' : 'Fizz') . ($i % 5 ? '' : 'Buzz') ?: $i) . "\n"),
 );
- +

- これで関数を呼び出している所は短くなった。では、$r$w$p、また'Fizz''Buzz'はどうやって 1行2文字に収めるのか。次のテクニックへ移ろう。 + これで関数を呼び出している所は短くなった。では、$r$w$p、また 'Fizz''Buzz' はどうやって 1行2文字に収めるのか。次のテクニックへ移ろう。

- +
-

余談: PHP 8.x で動作しなくてもいいなら

+

余談: PHP 8.x で動作しなくてもいいなら

今回使ったテクニックを説明する前に、余談として、文字列リテラルの短縮法として今回採用しなかったものを紹介する。

- +
  • @@ -335,22 +335,22 @@ a'
- +

- というルールがない場合、「未定義の定数が評価された場合、その定数の名前が値になる」という PHP 7.x までの仕様が利用できる。例えば、Fizzという文字列が欲しければ、次のようにする。 + というルールがない場合、「未定義の定数が評価された場合、その定数の名前が値になる」という PHP 7.x までの仕様が利用できる。例えば、 Fizz という文字列が欲しければ、次のようにする。

- +
$f
 =F
 .i
 .z
 .z
 ;;
- +

- こうして簡単に文字列を作れる。なお、この仕様は 7.x 時点でも警告を受けるので、@演算子を使って抑制してやるとよい。 + こうして簡単に文字列を作れる。なお、この仕様は 7.x 時点でも警告を受けるので、@ 演算子を使って抑制してやるとよい。

- +
$f
 =@
 F.
@@ -360,22 +360,22 @@ F.
 .#
 @z
 ;;
- +

むしろ、このことがわかっていたからこそ PHP 8.x での動作を要件に課したところがある。

- +
-

文字列リテラルの短縮

+

文字列リテラルの短縮

実際に使った手法の説明に移る。

- +

ずばり、文字列同士のビット演算を使う。PHP では、文字列同士でビット演算 (&|^) をした場合、文字列の各バイトごとに指定したビット演算がなされ、それを結合したものが演算結果となる。

- +
$a = "12345";
 $b = "world";
 
@@ -387,20 +387,20 @@ F.
 
 echo $result;
 // =&gt; F]AXQ
- +

これを踏まえ、次のコードを見てみよう。

- +
$x = "x\nOm\n";
 $y = "\nk!\no";
 $r = $x ^ $y;
 echo "$r\n";
- +

- 実行すると、rangeが表示される。さて、PHP では文字列リテラル中に生の改行を直接書いてもよいのだった (「主な障害」の節を参照のこと)。書きかえてみよう。 + 実行すると、range が表示される。さて、PHP では文字列リテラル中に生の改行を直接書いてもよいのだった (「主な障害」の節を参照のこと)。書きかえてみよう。

- +
$x
 ='x
 Om
@@ -413,11 +413,11 @@ o'
 
 $r = $x ^ $y;
 echo "$r\n";
- +

- さらに#を使って適当に調整すると、次のようになる。 + さらに # を使って適当に調整すると、次のようになる。

- +
$x
 =#
 'x
@@ -436,23 +436,23 @@ o'
 ;#
 
 echo "$r\n";
- +

- 1行あたり2文字で、rangeという文字列を生成することに成功した。他の必要な文字列にも、同様の処理をほどこす。 + 1行あたり2文字で、range という文字列を生成することに成功した。他の必要な文字列にも、同様の処理をほどこす。

- +

- 備考:Buzz中にある小文字のuは、このロジックだと non-printable な文字になってしまう。ここまでのテクニックを駆使すれば回避するのはそう難しくないので、考えてみてほしい。 + 備考: Buzz 中にある小文字の u は、このロジックだと non-printable な文字になってしまう。ここまでのテクニックを駆使すれば回避するのはそう難しくないので、考えてみてほしい。

- +
-

完成系

+

完成系

完成したものがこちら。

- +
&lt;?php
 
 $x
@@ -603,24 +603,24 @@ _!
 ')
 );
- +
-

感想など

+

感想など

PHP は、スクリプト言語の中だとシンタックスシュガーが少ない (体感)。この挑戦は不可能に思われたが、PHP マニュアルとにらめっこしていたらなんとかなった。

- +

みんなもプログラムを細長くしよう。

- +
-

余談2: 別解

+

余談2: 別解

- PHP では、バッククォートを使ってシェルを呼び出せる。これはshell_exec関数と等価である。さて、PHP ではバックスラッシュによる行継続が使えないと書いたが、シェルでは使える (当然だが、呼び出されるシェルに依存する。Bash なら大丈夫だろう。知らんけど)。 + PHP では、バッククォートを使ってシェルを呼び出せる。これは shell_exec 関数と等価である。さて、PHP ではバックスラッシュによる行継続が使えないと書いたが、シェルでは使える (当然だが、呼び出されるシェルに依存する。Bash なら大丈夫だろう。知らんけど)。

- +
&lt;?php
 
 printf(`
@@ -633,15 +633,15 @@ o\
 2\
 3\
 `);
- +

- なお、ここでは簡単のため出力にprintfをそのまま使っているが、実際にはprintfという文字列を合成して可変関数で呼び出す。 + なお、ここでは簡単のため出力に printf をそのまま使っているが、実際には printf という文字列を合成して可変関数で呼び出す。

- +

ただし、これでは

- +
  • @@ -649,15 +649,15 @@ o\
- +

に違反してしまう。スペースが使えないと引数とコマンドを区切れない。これは困った。

- +

もうこれ以上は不可能だと思っていたのだが、この記事の執筆中に解決する方法を思いついたので載せておく。

- +
&lt;?php
 
 $c = 'chr';
@@ -687,19 +687,19 @@ ${
 2\
 3\
 `);
- +

- 先程と同じく、chrprintfを生成する部分は長くなるので省いた。 + 先程と同じく、chrprintf を生成する部分は長くなるので省いた。

- +
${
 '_
 '}
- +

は変数で、中にはスペースとエスケープが入っている (chr(32) . chr(92))。シェルに渡されている文字列は次のようになる。

- +
e\
 c\
 h\
@@ -708,23 +708,23 @@ o\
 1\
 2\
 3\
- +

これは、前掲したコマンドと同じだ。かくして、スペースを陽に書かずにシェルをおおよそ自由に扱えるようになった。Fizzbuzz のワンライナーくらいすぐ書けるだろうから、あとはなんとかなるだろう (試してないけど)。

- +

ということでこれは別解ということにしておく。

- +

ちなみに、PHP 8.2 からは、この記法で Warning が出るようになるようだ。

- +
${
 '_
 '}
- +

最新版で警告が出るというのも美しくないので、私としては本編の解法を推す。

-- cgit v1.2.3-70-g09d2