From 994e0114d76ae19768d5c303874a968cf6369fd0 Mon Sep 17 00:00:00 2001 From: nsfisis Date: Thu, 7 Sep 2023 22:27:48 +0900 Subject: meta: migrate to monorepo --- .../content/posts/2021-03-05/my-first-post.xml | 27 + .../content/posts/2021-03-30/phperkaigi-2021.xml | 778 +++++++++++++++++++++ .../cpp-you-can-use-keywords-in-attributes.xml | 143 ++++ .../2021-10-02/python-unbound-local-error.xml | 87 +++ .../ruby-detect-running-implementation.xml | 122 ++++ .../2021-10-02/ruby-then-keyword-and-case-in.xml | 270 +++++++ .../rust-where-are-primitive-types-from.xml | 237 +++++++ ...ce-between-autocmd-bufwrite-and-bufwritepre.xml | 165 +++++ .../vim-swap-order-of-selected-lines.xml | 215 ++++++ .../posts/2022-04-09/phperkaigi-2022-tokens.xml | 563 +++++++++++++++ ...anner-write-tool-showing-banner-in-terminal.xml | 125 ++++ .../content/posts/2022-05-01/phperkaigi-2022.xml | 171 +++++ .../php-conference-okinawa-code-golf.xml | 122 ++++ .../support-for-communty-is-employee-benefits.xml | 72 ++ .../write-fizzbuzz-in-php-2-letters-per-line.xml | 708 +++++++++++++++++++ .../phperkaigi-2023-unused-token-quiz-1.xml | 188 +++++ .../2022-10-28/setup-server-for-this-site.xml | 313 +++++++++ .../phperkaigi-2023-unused-token-quiz-2.xml | 174 +++++ .../phperkaigi-2023-unused-token-quiz-3.xml | 329 +++++++++ .../2023-03-10/rewrite-this-blog-generator.xml | 97 +++ ...implementation-of-minimal-png-image-encoder.xml | 607 ++++++++++++++++ .../posts/2023-04-04/phperkaigi-2023-report.xml | 187 +++++ .../posts/2023-06-25/phpconfuk-2023-report.xml | 109 +++ 23 files changed, 5809 insertions(+) create mode 100644 vhosts/blog/content/posts/2021-03-05/my-first-post.xml create mode 100644 vhosts/blog/content/posts/2021-03-30/phperkaigi-2021.xml create mode 100644 vhosts/blog/content/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes.xml create mode 100644 vhosts/blog/content/posts/2021-10-02/python-unbound-local-error.xml create mode 100644 vhosts/blog/content/posts/2021-10-02/ruby-detect-running-implementation.xml create mode 100644 vhosts/blog/content/posts/2021-10-02/ruby-then-keyword-and-case-in.xml create mode 100644 vhosts/blog/content/posts/2021-10-02/rust-where-are-primitive-types-from.xml create mode 100644 vhosts/blog/content/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre.xml create mode 100644 vhosts/blog/content/posts/2021-10-02/vim-swap-order-of-selected-lines.xml create mode 100644 vhosts/blog/content/posts/2022-04-09/phperkaigi-2022-tokens.xml create mode 100644 vhosts/blog/content/posts/2022-04-24/term-banner-write-tool-showing-banner-in-terminal.xml create mode 100644 vhosts/blog/content/posts/2022-05-01/phperkaigi-2022.xml create mode 100644 vhosts/blog/content/posts/2022-08-27/php-conference-okinawa-code-golf.xml create mode 100644 vhosts/blog/content/posts/2022-08-31/support-for-communty-is-employee-benefits.xml create mode 100644 vhosts/blog/content/posts/2022-09-29/write-fizzbuzz-in-php-2-letters-per-line.xml create mode 100644 vhosts/blog/content/posts/2022-10-23/phperkaigi-2023-unused-token-quiz-1.xml create mode 100644 vhosts/blog/content/posts/2022-10-28/setup-server-for-this-site.xml create mode 100644 vhosts/blog/content/posts/2022-11-19/phperkaigi-2023-unused-token-quiz-2.xml create mode 100644 vhosts/blog/content/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3.xml create mode 100644 vhosts/blog/content/posts/2023-03-10/rewrite-this-blog-generator.xml create mode 100644 vhosts/blog/content/posts/2023-04-01/implementation-of-minimal-png-image-encoder.xml create mode 100644 vhosts/blog/content/posts/2023-04-04/phperkaigi-2023-report.xml create mode 100644 vhosts/blog/content/posts/2023-06-25/phpconfuk-2023-report.xml (limited to 'vhosts/blog/content/posts') diff --git a/vhosts/blog/content/posts/2021-03-05/my-first-post.xml b/vhosts/blog/content/posts/2021-03-05/my-first-post.xml new file mode 100644 index 00000000..92732269 --- /dev/null +++ b/vhosts/blog/content/posts/2021-03-05/my-first-post.xml @@ -0,0 +1,27 @@ + +
+ + My First Post + + これはテスト投稿です。これはテスト投稿です。これはテスト投稿です。 + + + + 2021-03-05 + 公開 + + + +
+ Test + + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod + tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim + veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea + commodo consequat. Duis aute irure dolor in reprehenderit in voluptate + velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint + occaecat cupidatat non proident, sunt in culpa qui officia deserunt + mollit anim id est laborum. + +
+
diff --git a/vhosts/blog/content/posts/2021-03-30/phperkaigi-2021.xml b/vhosts/blog/content/posts/2021-03-30/phperkaigi-2021.xml new file mode 100644 index 00000000..f8813273 --- /dev/null +++ b/vhosts/blog/content/posts/2021-03-30/phperkaigi-2021.xml @@ -0,0 +1,778 @@ + +
+ + PHPerKaigi 2021 + + 2021-03-26 から 2021-03-28 にかけて開催された、PHPerKaigi 2021 に参加した。 + + + conference + php + phperkaigi + + + + 2021-03-30 + 公開 + + + +
+ PHPerKaigi 2021 参加レポ + + 2021-03-26 から 2021-03-28 + にかけて開催された、 PHPerKaigi 2021 + に一般参加者として参加した。 + 弊社 デジタルサーカス株式会社 + (今年1月から勤務) + はダイヤモンドスポンサーとなっており、スポンサー枠のチケットを使わせていただいた。 + + + このようなカンファレンスには初めて参加するのでかねてより心待ちにしていたのだが、生憎2日目から体調を崩してしまい、この記事も途中までとなっている。まだ見ていないセッションも多いが、ひとまず現時点での参加レポを書いておく。 + + + 発表はトラック A、B に分かれていたのだが、今回はすべて A + トラックを視聴している (切り替えるのが面倒だっただけ)。 + +
+ 凡例 +
+ + 発表・スライドのメモ (引用ではない) + +
+ + 感想など + +
+
+ Day 0 前夜祭 (2021/03/27) +
+ 17:30 [A] + + PHP で AWS Lambda + +
+ + Rails のプロジェクトを PHPer のメンバのみでメンテ →他のメンバもわかる + PHP にリプレースを検討 + + + サーバレス + サーバ・インフラの管理が不要 + アプリケーションコードの知識だけで保守可能 + + + ゼロベースで作れる案件が (Railsの件とは別に) + あるため、そちらで試験的に導入? + + + AWSの学習 AWS のドキュメント DevelopersIO + + + AWS Lambda のカスタムランタイムで PHP を動かす + + + サーバのセットアップや維持管理を気にしなくて良い サーバーレスで PHP + を動かすツールがすでにある サーバーレス構築はすんなり + + + 今は Laravel がルーティングしている Laravel Livewire を Lambda + に載せられないか? デプロイ方法は? バッチ処理は? (Lambda は + 15分の制限) + + + Lambda でコンテナイメージがサポートされるように + + + 抽象化されたもの「だけ」しか知らないよりも具象の理解は助けになる + +
+ + AWS Lambda のような Function as a Service + はマイクロサービス化における一つの到達点に思えるのだが、これを使って実際に + web サービスを作る具体的なイメージがまだ見えない (注: すべて for me + として書いている)。 + + + PHP on AWS Lambda があれだけ簡単に動かせるのには驚いた。 + + + 勝手に AWS Lambda だとフットプリントの軽さが求められそう (= PHP + + Laravel などでは動かなさそう) + だという先入観を持っていたのだが、この発表のデモによればそうでもないらしい。 + +
+
+ 18:10 [A] + + 大規模サイトの SEO + +
+ + 大規模サイト (100万ページ以上) Google の基準 + + + クロールバジェットを意識したSEO + + + 大規模サイトでコンテンツが中頻度 (1回/週) で更新 OR 中規模サイト + (10,000以上) でコンテンツが目まぐるしく変更される + これを満たさないなら、クロールバジェットを考えなくてもいい + + + サーチコンソール 「カバレッジ」の「除外」 + 多すぎるのは問題→クロールバジェットを浪費している + + + クエリの順番を決める + 空の値のルールを決めておく + リダイレクトすればインデックスはうまくいく + リンクが存在する限りクロールはされる + + + リニューアル前のURL + + + インデックスは移行される + リンクのURLが存在する限り、別のURLとしてクロールされる + リダイレクトされるとはいえ、リニューアル前のURLは移行した方が良い + リニューアルで無視されるようになったパラメータも注意 + + + robotes.txt で拒否しているのにクロールされる 一時的に拒否を外して 404 や + 301 を読ませる 内部リンクを確認する JS でのリンクに書き換え + + + クエリパラメータからURLのパスに /tokyo?area=HOGE/tokyo/HOGE + + + URL 設計だいじ + +
+ + SEO (Search Engine Optimization) + は大して知らないので新鮮な話が多かった。その分語れることも少ない……。 + +
+
+ 18:50 [A] +
+ + 知覚可能 操作可能 理解可能 堅牢 ちゃんとしたHTMLを書く + (閉じタグ・入れ子構造など) + + + 標準の HTML を適切に使う + WAI-ARIA + キーボードフレンドリー + マシンフレンドリー + SEOフレンドリー + + + button タグ →キーボード h1 タグ →スクリーンリーダー・クローラ a タグ + + + WAI-ARIA HTML では表現できないセマンティクスを追加する + + + + ロール + + 何をするのか? + ユーザーアクションによって変化しない + + + + プロパティ + + 関連づけられたデータ + + + + ステート + + 現在の状態 + + + + + まずは標準の HTML 要素で解決する 何でもかんでも WAI-ARIA + を使えばいいというものではない + + + マウスホバーでツールチップが出てくるが、キーボード操作では出てこない + + + VoiceOver + + + 全ての属性を使う必要はない + あくまでアクセシビリティを上げるための方法の一つにすぎない + +
+ + つい最近 WAI-ARIA + についての記事を読んだばかりだったので個人的にタイムリーな話題だった。(あまりこの言葉を使いたくないのだが) + いわゆる「健常者」にとって、こうした問題を普段の生活の中で意識するのは難しい。だからこそ情報へのアンテナは張っておくようにしたい。 + +
+
+ 19:30 [A] + + PHP で FUSE + + + 個人的に楽しみだった発表。 + +
+ + VFS (virtual filesystem) vs 具体的なファイルシステム + + + 最適な実装方法は状況により異なる + + + アプリケーションに見せるAPIは変えずに実装を隠蔽する→VFS + + + カーネルのプログラムを作るのは難しい + * 権限がデカすぎる + * システム全体がクラッシュ + * セキュリティリスク + * 開発サイクルを回しづらい + * ネイティブコードにコンパイルされる言語である必要がある + + + Filesystem in USEr space (FUSE) + + + 特定の C の関数を呼ぶことで filesystem が作れる + FFI を持つ言語なら FUSE が使える + + + SSHFS / s3fs / Docker Desktop + + + Linux 以外でも使える + + + dokany (on Windows) + osxfuse + + + VFS: システムコールが呼ばれると、ファイルシステムによってコール FUSE: + カーネル空間からユーザ空間へ通信 + + + 高レベルなラッパで型をつける + + + PHP 以外では Wordpress を FUSE にマウントする実装がある (C, Python など) + + + grep できる + sed できる + 編集できる + +
+ + 期待通りの興味深い発表だった。FUSE + 自体も今回の発表で知ったのだが、これ本体の実装を見るのも面白そうだ。 + この発表を聞きながらファイルシステムにマウントできそうなものを考えていたのだが、およそ木構造をしているものすべてと言えそうだ + (ハンマーしか持っていないと云々)。何かできそうだがなかなか思いつかない。 + +
+
+
+ Day 1 (2021/03/27) +
+ 10:50 [A] + + ATDD + +
+ + ユーザーストーリー + ユニットテスト + CI/CD + + + ユーザストーリーの受け入れ条件が曖昧になりがち + デグレチェックがユニットレベルでは収まらない場合、手動で同じシナリオをテストしている + + + Q2の強化 アジャイルテストの4象限 + + + 技術面/ビジネス面 + 開発チーム支援(コーディング前・コーディング中)/製品批評(コーディング後) + + + + Q1: 技術面 & チーム支援 + + TDD + ユニットテストなど + + + + Q2: ビジネス面 & チーム支援 + + ATDD + ビジネス面の受け入れテストで駆動する + + + + + Agile Alliance ユーザストーリーのスキルレベルを高める + + + テストピラミッド + + + UI Tests + Service Tests + Unit Tests + 異なる粒度のテストを書く + + 高レベルになるほど、持つべきテストは少なくなる + + ピラミッド型になる + + + + + 高レベルテストが多すぎる→アイスクリームコーン アンチパターン + + + ATDD (Acceptance TDD) API経由・UI経由での高レベルテスト E2E test + + + ストーリ受け入れテスト + + + 入れ子のフィードバックループ ATDD(外側) と TDD(内側) + + + 外部品質・内部品質 + + + バーティカルスライスのデリバリー + + + cucumber + gauge + behat + + + ユビキタス言語 手動テストもspecに書く 自動化は可能だがコスパが悪い + 失敗することがわかっているテスト(レッドテスト)はCIから外す + 失敗時の原因究明が難しい 饒舌なエラーメッセージ 状況のスナップショット + + + Continuous Testing + +
+ + User Acceptance Test (UAT) + くらいの規模になると個人開発・趣味開発では触れない領域なので、大いに勉強になった。スライドに添付されている資料が相当に充実していたので、これを読むのが本番といった様相すら感じる。 + 高レベルテストの自動化は現在のプロジェクトでも感じており、自動化のチャンスは伺っている。とはいえセッションでも指摘されているように自動化することにコストがかかりすぎる領域があるのも事実で、そのバランスが難しい。 + +
+
+ 11:50 [A] + + 型解析を用いたリファクタリング + + + 型のある世界で生きてきた身として大いに楽しみにしていた発表。 + +
+ + PHPStan + Phan + Psalm + + + autoload も認識できる bootstrapFiles + + + 編集箇所と利用箇所を CI でチェック ルールレベルを徐々に引き上げていく + 警告が多すぎると見落としてしまう・無視されやすくなる + + + 型がついていないことによるエラーが多い + + + 型よりも詳細な検査 Util_Assert::min + + + SQL を静的解析 placeholder の型付け + + + 警告レベルを低いレベルから導入 タイプヒントを積極的に書いていく PHPStan + の拡張を追加する + +
+ + 昨今、動的型付き言語での型宣言・型アノテーション・型ヒントの導入が相次いでいる。長らく静的型付き言語を書いてきた私からすると、ようやく気づいたかといったところだが、ともかく型を導入する言語が増えてきた。 + 今のプロジェクトでも新しく追加するコードには型をつけるよう努めているが、どうしても古いコードには型がついていない。個人的には型のないコードに対してどう型を自動的に付けるかという点に興味があり、その点で + Ruby の typeprof には注目している。 + +
+
+ 12:30 [A] + + 昼食をとっていた。事前に何か食料を買っておくべきだった。 + +
+
+ 13:10 [A] + + Documentation as Code + + + この発表も以前から非常に楽しみにしていた。 + +
+ + 開発開始までのオーバーヘッド 新規にチームにジョイン + 担当範囲外の機能を理解 オンボーディングのコスト + + + PHPerKaigi 2020 で発表あり + + + 継続的にシステムの理解を助けるドキュメント + + + 継続的ドキュメンテーション システムとドキュメントの乖離 + + + 書いてあることが間違っている・足りない * 徐々にずれていく * + システムの更新タイミングとドキュメントの更新タイミングに差がある + + + システムとドキュメントは対応関係がある * 間違ったドキュメント * + 存在しないドキュメント + + + システムとドキュメントの乖離を定量化する 継続的に + システムの更新に近いタイミングで ドキュメントを更新し続ける + + + Documentation as Code + + + コードと同じツールでドキュメントを書く * issue tracker * vcs * plain + text markup * automation + + + 開発者 システム ドキュメント 構造化データ ソフトウェア + + + システムから構造化データを抽出する PHPDoc OpenAPI + + + ビュー 関心ごとに合わせてアーキテクチャを一つ以上の側面(断面)で説明する + + + ビューの単位でドキュメントに + + + スタックトレースからのドキュメント生成 + +
+ + ドキュメントの管理は現プロジェクトでも課題と感じている。作られた当初は正しくても、実態と乖離していくのを止めるのは困難を極める。全体的に興味深い発表だったが、特にスタックトレースからのドキュメント生成というアイデアに惹かれるものを感じた。スタックトレースという実態と不可分な + (乖離しない) + 情報を起点にするのは理にかなっている。問題はトレースをいつ、どう取るかだろうか。それを自動化しなければ、実態との乖離が避けられないだろう。 + +
+
+ 14:10 [A] + + cookie による session 管理 + + + 全体的に基本的な話だったので特に触れない。Cookie + やセッションの話としては非常に分かりやすくまとめられていたので、知らない人が学ぶにはいい教材だろう。 + +
+
+ 14:50 [A] + + PHP のエラーと例外 + +
+ + エラー PHPエンジンがエラーを通知する 例外 プログラムが投げる + + + PHP7-8とエラー + + + PHPエンジンのエラーの一部が に変換されるようになった → try-catch + で捕捉できる + + + は例外とは異なる + + + PHP8 でエラーレベルの引き上げ + + + + 捕捉すべきもの + + recoverable + + + + 捕捉すべきでないもの + + unrecoverable + 開発時に対処できるもの + + + + + 例外 * 捕捉して事後処理 * 捕捉せず(or 捕捉した上で)さらに上に是非を問う + + + 開発段階で例外を把握し、ハンドリングを考えておく + + + と + + + はキャッチすべきでない + + + + + + + + 本番で起きてはいけない + + + + + + + + 本番で起きてはいけない →生じないのだから捕捉もしない + + + + + + + + 起こるかもしれないので本番環境でも考慮する + + + + + 捕捉して対応するのではなく、未然に防ぐ + + + 独自例外を使う を投げてしまうと、 catch ()せざるを得ない →catch + 範囲が広すぎる + + + SPL の例外を使う + + + 例外翻訳 + 上位のレイヤが下位のレイヤの例外を捕捉し、上位レイヤのAPIに「翻訳」する + 下位レイヤの知識に依存させない + + + @throws 捕捉してほしい例外を書き連ねておく + + + 呼び出しもとに負わせたい責任 + +
+ + PHP を学んでいる途中の私としては、今まさに聞きたい発表だった (現時点で + PHP を書き始めてから 4ヶ月ほどになる)。 + + + 個人的に例外やエラーを最もうまく扱っているのは Go、Swift、Rust、Haskell + などのエラーを「値として」扱う言語だと思っている。try-catch + は通常の処理フローを完全に壊してしまう上、構文としても重すぎる。値としてのエラー通知は + C言語時代への回帰ともいえるが、その頃と異なるのはエラーを暗黙のうちに握り潰すことがないということだ。これらの言語は型を持っており、静的に検証ができる + (C のそれはまともな型付けではない。念のため)。 + + + PHP + のように、すでに例外が言語システムに根ざしている言語ではどうすればよいか。この場合も同じく静的検証の力を借りることになるだろう。 + +
+
+ 15:30 [A] + + Laravel のメール認証 + + + Laravel + の知識がない私にはまったくついていけなかった。また、個人的にタイトルがややミスリーディングに感じた。 + +
+
+ 16:10 [A] + + gRPC + +
+ + Unary RPCs Server streaming RPCs Client streaming RPCs Bidirectional + streaming RPCs + + + Protobuf + + + 実装とAPIが乖離しにくい 自動生成 複数言語でも相互に使える + + + マイクロサービスのサービス通信 スマホアプリ ゲームサーバ + + + PHP では? + + + PHP ではストリーミングが難しい リクエストごとにプロセスが使い捨て + + + PHP ではgRPCのクライアントしか対応していない + + + gRPC-Web ブラウザで扱うためのJSライブラリ+プロトコル + + + HTTP/1.1 でも使える Unary RPC と Server streaming RPC のみ + + + Envoy Nginx などで相互に gRPC と gRPC-Web で変換 + + + Amp イベント駆動な並行処理のフレームワーク + + + HTTP/2 対応 + + + C#のgRPC-Webが楽 + +
+ + (発表の中でもまさに同じことをおっしゃっていたが) PHP + 以外の方が向いているだろう、というのが第一の感想である。gRPC + はそれ自体というよりも Protobuf + というエコシステムに乗れることのメリットが大きいと感じる。そのエコシステムにうまく乗れない時点で、うーんという感じ。 + +
+
+ 16:50 [A] + + アーキテクチャテスト + +
+ + Independent Core Layer Pattern + + + 開発初期のアーキテクチャが崩れる + アーキテクチャ観点のコードレビューができない + + + どこにクラスを置けばよいか? ドキュメントがない + + + アーキテクチャ設計に関する知識が属人化・暗黙知化 + + + ガイドライン * 最初にルールを決めるのは簡単 * + ルール通り作り始めるのも簡単 * + →維持するのが難しい、人が決めたものゆえ壊れやすい + + + PHP の特性 * クラスは public * 可視性の制御が public / protected / + private のみ * 依存関係の制御が困難 + + + アーキテクチャテスト + クラスの依存関係や実装ルールをコードとして表現し、自動テスト化する + + + deptrac + phpat + + + Independent Core Layer Pattern + + + アーキテクチャテストの失敗 * 実装誤り * or アーキテクチャが適切でない * + 開発の過程でフィードバックしていく + + + モジュラーモノリス→マイクロサービスへ + +
+
+
+
+ Day 2 (2021/03/28) + + 冒頭に書いた通り、2日目から体調が悪くまともに聴けていない。途中までは頭痛を我慢しつつ見ていたのだが、まともに入ってこなかった。 + + + 残念ではあるが、いずれにせよ見られていない発表は他にもあるので、今週末にでもまとめて見ようと思う。 + +
+
+ 全体の感想 + + Day 2 + にほとんど参加できなかったのは残念だが、イベント自体は大変楽しく、また興味深いものであった。自分がまったく知らない領域の話を聞けるのはこうしたイベントならではだと感じる。オンライン開催ゆえ現地に行く必要がなく、気軽に参加できたのも + (特に初参加者として) 嬉しいポイントだった。 + + + 今回、雑談/登壇者への質問等向けに Discord + サーバもあったのだが、こちらは参加こそしたものの ROM + のままになってしまった。発表に1ウィンドウ、メモを書くのに1ウィンドウ、Discord + 表示に + 1ウィンドウで私にはもう脳のリソースとディスプレイのスペースが追いつかなかった + (さらにいうと Zoom + でアンカンファレンスもやっていたようだ。こちらはまったく参加していない)。 + + + 1つ個人的な反省点としては、一つ一つのセッションを真剣に聞き過ぎたというものがある。もっと適当に聞いておけばよかった。これだけだと大変語弊があるのだが、言い方を変えると、Discord + しかりアンカンファレンスしかり「このイベントのこの瞬間にしかないコンテンツ」に触れずに、後から見返せる発表やスライドに注力してしまった、ということだ。発表の詳細な見直しはあとからできるのだから、今しかできないことを考えるべきだった。 + まあ初カンファレンスだし、とお茶を濁しておこう。 + + + さて、カンファレンスで一つ気になったことがある。それは、Discord + という書き込み場所が増えたことでニコ生のコメントの流量が吸い取られてしまったのではないか、という点だ。ニコニコだけ見ていると過疎っているかのように見えた発表も、Discord + の方では盛り上がっている、というのを何度か見かけた。ニコニコのコメント方式は盛り上がりを如実に反映するが、逆もまたしかり。Discord + があったこと自体はプラスだったと思うが、この点はマイナスだったのではないかと感じる。 + + +
+
+ + 最後になりましたが、毎年の PHPerKaigi + 開催にご尽力されている皆様、スピーカーの皆様、楽しい3日間でした。ありがとうございました! + (ずっと常体で書いてしまったのでいきなり仏頂面から笑顔になったようで気持ち悪い) + + + ではまた来年。 + +
+
+
diff --git a/vhosts/blog/content/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes.xml b/vhosts/blog/content/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes.xml new file mode 100644 index 00000000..f87d3a65 --- /dev/null +++ b/vhosts/blog/content/posts/2021-10-02/cpp-you-can-use-keywords-in-attributes.xml @@ -0,0 +1,143 @@ + +
+ + 【C++】 属性構文の属性名にはキーワードが使える + + C++ の属性構文の属性名には、キーワードが使える。ネタ記事。 + + + cpp + cpp17 + + + + 2021-10-02 + Qiita から移植 + + + + + この記事は Qiita から移植してきたものです。 + 元 URL: https://qiita.com/nsfisis/items/94090937bcf860cfa93b + + + タイトル落ち。まずはこのコードを見て欲しい。 + + + + + [[alignas]] [[alignof]] [[and]] [[and_eq]] [[asm]] [[auto]] [[bitand]] + [[bitor]] [[bool]] [[break]] [[case]] [[catch]] [[char]] [[char16_t]] + [[char32_t]] [[class]] [[compl]] [[const]] [[const_cast]] [[constexpr]] + [[continue]] [[decltype]] [[default]] [[delete]] [[do]] [[double]] + [[dynamic_cast]] [[else]] [[enum]] [[explicit]] [[export]] [[extern]] [[false]] + [[final]] [[float]] [[for]] [[friend]] [[goto]] [[if]] [[inline]] [[int]] + [[long]] [[mutable]] [[namespace]] [[new]] [[noexcept]] [[not]] [[not_eq]] + [[nullptr]] [[operator]] [[or]] [[or_eq]] [[override]] [[private]] + [[protected]] [[public]] [[register]] [[reinterpret_cast]] [[return]] [[short]] + [[signed]] [[sizeof]] [[static]] [[static_assert]] [[static_cast]] [[struct]] + [[switch]] [[template]] [[this]] [[thread_local]] [[throw]] [[true]] [[try]] + [[typedef]] [[typeid]] [[typename]] [[union]] [[unsigned]] + [[virtual]] [[void]] [[volatile]] [[wchar_t]] [[while]] [[xor]] [[xor_eq]] + // [[using]] + int main() { + std::cout << "Hello, World!" << std::endl; + } + ]]> + +
+ + コンパイラのバージョン $ clang++ –version Apple clang version 11.0.0 + (clang-1100.0.33.8) Target: x86_64-apple-darwin19.6.0 Thread model: + posix InstalledDir: + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin + + + コンパイルコマンド (C17指定) $ clang –std=c++17 hoge.cpp + +
+ + この記事から得られるものはこれ以上ないので以下は蛇足になる。 + + + 別件で cppreference.com の + identifier + のページ を読んでいた時、次の文が目に止まった。 + +
+ + + the identifiers that are keywords cannot be used for other purposes; + + + The only place they can be used as non-keywords is in an + attribute-token. (e.g. [[private]] is a valid attribute) (since C++11) + + + + +
+ + キーワードでも属性として指定する場合は非キーワードとして使えるらしい。 + 実際にやってみる。 + + + 同サイトの keywords のページ + から一覧を拝借し、上のコードが出来上がった (C++17 + においてキーワードでないものなど、一部省いている)。 大量の警告 (unknown + attribute `〇〇' ignored) + がコンパイラから出力されるが、コンパイルできる。 + + + 上のコードでは [[using]] をコメントアウトしているが、これは using + キーワードのみ属性構文の中で意味を持つからであり、このコメントアウトを外すとコンパイルに失敗する。 + + + + + + C++17 の仕様も見てみる (正確には標準化前のドラフト)。 + + + 引用元: https://timsong-cpp.github.io/cppwp/n4659/dcl.attr#grammar-4 + +
+ + If a keyword or an alternative token that satisfies the syntactic + requirements of an identifier is contained in an attribute-token, it is + considered an identifier. + +
+ + 「identifier の構文上の要件を満たすキーワードまたは代替トークンが + attribute-token に含まれている場合、identifier + とみなされる」とある。どうやら間違いないようだ。 + + + ところで、代替トークン (alternative token) とは and (&) や bitor + (|) などのことだが、identifier + の構文上の要件を満たさないような代替トークンなどあるのか? + 疑問に思って調べたところ、代替トークンという語にはダイグラフも含まれるらしい + (参考: + 同ドラフト) + + + <%{ + %>} + <:[ + :>] + %:# + %:%:## + + + 「identifier + の構文上の要件を満たさないような代替トークン」はこれらが当てはまると思われる。 + + + 調べた感想: 字句解析器か構文解析器が辛そう + +
diff --git a/vhosts/blog/content/posts/2021-10-02/python-unbound-local-error.xml b/vhosts/blog/content/posts/2021-10-02/python-unbound-local-error.xml new file mode 100644 index 00000000..eb8aa452 --- /dev/null +++ b/vhosts/blog/content/posts/2021-10-02/python-unbound-local-error.xml @@ -0,0 +1,87 @@ + +
+ + 【Python】 クロージャとUnboundLocalError: local variable 'x' referenced before assignment + + Python における UnboundLocalError の理由と対処法。 + + + python + python3 + + + + 2021-10-02 + Qiita から移植 + + + + + この記事は Qiita から移植してきたものです。 + 元 URL: https://qiita.com/nsfisis/items/5d733703afcb35bbf399 + + + 本記事は Python 3.7.6 の動作結果を元にして書かれている。 + + + Python でクロージャを作ろうと、次のようなコードを書いた。 + + + + + + 関数 g から 関数 f のスコープ内で定義された変数 x を参照し、それに + 1 を足そうとしている。 これを実行すると x += 1 + の箇所でエラーが発生する。 + +
+ + UnboundLocalError: local variable `x' referenced before assignment + +
+ + local変数 x が代入前に参照された、とある。これは、fx + を参照するのではなく、新しく別の変数を g 内に作ってしまっているため。 + 前述のコードを宣言と代入を便宜上分けて書き直すと次のようになる。var + を変数宣言のための構文として擬似的に利用している。 + + + + + + 当初の意図を表現するには、次のように書けばよい。 + + + + + + (*) のように、nonlocal を追加する。これにより一つ外側のスコープ (g + の一つ外側 = f) で定義されている x を探しに行くようになる。 + +
diff --git a/vhosts/blog/content/posts/2021-10-02/ruby-detect-running-implementation.xml b/vhosts/blog/content/posts/2021-10-02/ruby-detect-running-implementation.xml new file mode 100644 index 00000000..7c0c960d --- /dev/null +++ b/vhosts/blog/content/posts/2021-10-02/ruby-detect-running-implementation.xml @@ -0,0 +1,122 @@ + +
+ + 【Ruby】 自身を実行している処理系の種類を判定する + + Ruby には複数の実装があるが、自身を実行している処理系の種類をスクリプト上からどのように判定すればよいだろうか。 + + + ruby + + + + 2021-10-02 + Qiita から移植 + + + + + この記事は Qiita から移植してきたものです。 + 元 URL: https://qiita.com/nsfisis/items/74d7ffeeebc51b20d791 + + + Ruby + という言語には複数の実装があるが、それらをスクリプト上からどのようにして + programmatically に見分ければよいだろうか。 + + + Object クラスに定義されている RUBY_ENGINE + という定数がこの用途に使える。 + + + 参考: + Object::RUBY_ENGINE + + + 上記ページの例から引用する: + + + + + + それぞれの処理系がどのような値を返すかだが、stack overflow + に良い質問と回答があった。 + + + What values for RUBY_ENGINE + correspond to which Ruby implementations? より引用: + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
RUBY_ENGINEImplementation
<undefined>MRI < 1.9
`ruby'MRI >= 1.9 or REE
`jruby'JRuby
`macruby'MacRuby
`rbx'Rubinius
`maglev'MagLev
`ironruby'IronRuby
`cardinal'Cardinal
+
+ + なお、この質問・回答は + 2014年になされたものであり、値は変わっている可能性がある。MRI (aka + CRuby) については執筆時現在 (2020/12/8) も 'ruby' + が返ってくることを確認済み。 + + + この表にない主要な処理系として、https://mruby.org[mruby] は 'mruby' + を返す。 + + + mruby + 該当部分のソース より引用: + + + + +
diff --git a/vhosts/blog/content/posts/2021-10-02/ruby-then-keyword-and-case-in.xml b/vhosts/blog/content/posts/2021-10-02/ruby-then-keyword-and-case-in.xml new file mode 100644 index 00000000..0a799a3b --- /dev/null +++ b/vhosts/blog/content/posts/2021-10-02/ruby-then-keyword-and-case-in.xml @@ -0,0 +1,270 @@ + +
+ + 【Ruby】 then キーワードと case in + + Ruby 3.0 で追加される case in 構文と、then キーワードについて。 + + + ruby + ruby3 + + + + 2021-10-02 + Qiita から移植 + + + + + この記事は Qiita から移植してきたものです。 + 元 URL: https://qiita.com/nsfisis/items/787a8cf888a304497223 + +
+ TL; DR + + case - in によるパターンマッチング構文でも、case - when + と同じように then が使える (場合によっては使う必要がある)。 + +
+
+ <literal>then</literal> とは + + 使われることは稀だが、Ruby では then + がキーワードになっている。次のように使う: + + + + + + このキーワードが現れうる場所はいくつかあり、ifunlessrescuecase + 構文がそれに当たる。 上記のように、何か条件を書いた後 then + を置き、式がそこで終了していることを示すマーカーとして機能する。 + + + + +
+
+ なぜ普段は書かなくてもよいのか + + 普通 Ruby のコードで then + を書くことはない。なぜか。次のコードを実行してみるとわかる。 + + + + + + 次のような構文エラーが出力される。 + + + + + + 二つ目のメッセージは無視して一つ目を読むと、then; + か改行が来るはずのところ変数だかメソッドだかが現れたことによりエラーとなっているようだ。 + + + ポイントは改行が then (や ;) の代わりとなることである。true + の後に改行を入れてみる。 + + + + + + 無事 Hello, World! と出力されるようになった。 + +
+
+ なぜ <literal>then</literal> や <literal>;</literal> や改行が必要か + + なぜ then; や改行 (以下 「then 等」) + が必要なのだろうか。次の例を見てほしい: + + + + + + then; + も改行もないのでエラーになるが、これは条件式がどこまで続いているのかわからないためだ。 + この例は二通りに解釈できる。 + + + + + + + + + then 等はこの曖昧性を排除するためにあり、条件式は if から then + 等までの間にある、ということを明確にする。 C系の if 後に来る (/) + や、Python の :、Rust/Go/Swift などの { も同じ役割を持つ。 + + + Ruby の場合、プログラマーが書きやすいよう改行でもって then + が代用できるので、ほとんどの場合 then は必要ない。 + +
+
+ <literal>case</literal> - <literal>in</literal> における <literal>then</literal> + + ようやく本題にたどり着いた。来る Ruby 3.0 では casein + キーワードを使ったパターンマッチングの構文が入る予定である。この構文でもパターン部との区切りとして + then 等が必要になる。 (現在の) Ruby には formal + な形式での文法仕様は存在しないので、yacc の定義ファイルを参照した (yacc + の説明は省略)。 + + + https://github.com/ruby/ruby/blob/221ca0f8281d39f0dfdfe13b2448875384bbf735/parse.y#L3961-L3986 + + + command_start = FALSE; + $1 = p->ctxt; + p->ctxt.in_kwarg = 1; + $$ = push_pvtbl(p); + } + { + $$ = push_pktbl(p); + } + p_top_expr then + { + pop_pktbl(p, $3); + pop_pvtbl(p, $2); + p->ctxt.in_kwarg = $1.in_kwarg; + } + compstmt + p_cases + { + /*%%%*/ + $$ = NEW_IN($4, $7, $8, &@$); + /*% %*/ + /*% ripper: in!($4, $7, escape_Qundef($8)) %*/ + } + ; + ]]> + + + 簡略版: + + + + + + ここで、keyword_in は文字通り inp_top_expr + はいわゆるパターン、thenthen + キーワードのことではなく、この記事で then 等と呼んでいるもの、つまり + then キーワード、;、改行のいずれかである。 + + + これにより、case - when による従来の構文と同じように、then + 等をパターンの後ろに挿入すればよいことがわかった。つまり次の3通りのいずれかになる: + + + + + + ところで、p_top_expr には if による guard clause + が書けるので、その場合は if - then と似たような見た目になる。 + + + + +
+
+ まとめ + + + ifcase の条件の後ろには then;、改行のいずれかが必要 + + 通常は改行しておけばよい + + + 3.0 で入る予定の case - in でも then 等が必要になる + Ruby の構文を正確に知るには (現状) parse.y を直接読めばよい + +
+
diff --git a/vhosts/blog/content/posts/2021-10-02/rust-where-are-primitive-types-from.xml b/vhosts/blog/content/posts/2021-10-02/rust-where-are-primitive-types-from.xml new file mode 100644 index 00000000..3aaca63f --- /dev/null +++ b/vhosts/blog/content/posts/2021-10-02/rust-where-are-primitive-types-from.xml @@ -0,0 +1,237 @@ + +
+ + Rust のプリミティブ型はどこからやって来るか + + Rust のプリミティブ型は予約語ではなく普通の識別子である。どのようにこれが名前解決されるのかを調べた。 + + + rust + + + + 2021-10-02 + Qiita から移植 + + + + + この記事は Qiita から移植してきたものです。 + 元 URL: https://qiita.com/nsfisis/items/9a429432258bbcd6c565 + +
+ 前置き + + Rust + において、プリミティブ型の名前は予約語でない。したがって、次のコードは合法である。 + + + + + + では、普段単に bool と書いたとき、この bool + は一体どこから来ているのか。rustc のソースを追ってみた。 + +
+ + 前提知識: 一般的なコンパイラの構造、用語。rustc そのものの知識は不要 + (というよりも筆者自身がよく知らない) + +
+
+
+ 調査 + + 調査に使用したソース (調査時点での最新 master) + + + https://github.com/rust-lang/rust/tree/511ed9f2356af365ad8affe046b3dd33f7ac3c98 + + + どのようにして調べるか。rustc + の構造には詳しくないため、すぐに当たりをつけるのは難しい。 + + + 大雑把な構造としては、compiler フォルダ以下に rustc_* + という名前のクレートが数十個入っている。これがどうやら rustc + コマンドの実装部のようだ。 + + + rustc はセルフホストされている (= rustc 自身が Rust で書かれている) + ので、boolchar + などで適当に検索をかけてもノイズが多すぎて話にならない。 + しかし、お誂え向きなことに i128/u128 + というコンパイラ自身が使うことがなさそうな型が存在するのでこれを使って + git grep してみる。 + + + + + + 165 + 程度であれば探すことができそうだ。今回は、クレート名を見ておおよその当たりをつけた。 + + + + + + rustc_resolve + というのはいかにも名前解決を担いそうなクレート名である。該当箇所を見てみる。 + + + , + } + + impl PrimitiveTypeTable { + fn new() -> PrimitiveTypeTable { + let mut table = FxHashMap::default(); + + table.insert(sym::bool, Bool); + table.insert(sym::char, Char); + table.insert(sym::f32, Float(FloatTy::F32)); + table.insert(sym::f64, Float(FloatTy::F64)); + table.insert(sym::isize, Int(IntTy::Isize)); + table.insert(sym::i8, Int(IntTy::I8)); + table.insert(sym::i16, Int(IntTy::I16)); + table.insert(sym::i32, Int(IntTy::I32)); + table.insert(sym::i64, Int(IntTy::I64)); + table.insert(sym::i128, Int(IntTy::I128)); + table.insert(sym::str, Str); + table.insert(sym::usize, Uint(UintTy::Usize)); + table.insert(sym::u8, Uint(UintTy::U8)); + table.insert(sym::u16, Uint(UintTy::U16)); + table.insert(sym::u32, Uint(UintTy::U32)); + table.insert(sym::u64, Uint(UintTy::U64)); + table.insert(sym::u128, Uint(UintTy::U128)); + Self { primitive_types: table } + } + } + ]]> + + + これは初めに列挙したプリミティブ型の一覧と一致している。doc comment + にも、 + +
+ + All other types are defined somewhere and possibly imported, but the + primitive ones need special handling, since they have no place of + origin. + +
+ + とある。次はこの struct + の使用箇所を追う。追うと言っても使われている箇所は次の一箇所しかない。なお説明に不要な箇所は大きく削っている。 + + + Option> { + // (略) + + if ns == TypeNS { + if let Some(prim_ty) = self.primitive_type_table.primitive_types.get(&ident.name) { + let binding = + (Res::PrimTy(*prim_ty), ty::Visibility::Public, DUMMY_SP, ExpnId::root()) + .to_name_binding(self.arenas); + return Some(LexicalScopeBinding::Item(binding)); + } + } + + None + } + ]]> + + + 関数名や doc comment が示している通り、この関数は識別子 (identifier, + ident) を現在のレキシカルスコープ内で解決 (resolve) する。 + if ns == TypeNS のブロック内では、primitive_type_table (上記の + PrimitiveTypeTable::new() で作られた変数) に含まれている識別子 + (booli32 など) + かどうか判定し、そうであればそれに紐づけられたプリミティブ型を返している。 + + + なお、ns は「名前空間」を示す変数である。Rust + における名前空間はC言語におけるそれとほとんど同じで、今探している名前が関数名/変数名なのか型なのかマクロなのかを区別している。この + if + は、プリミティブ型に解決されるのは型を探しているときだけだ、と言っている。 + + + 重要なのは、これが resolve_ident_in_lexical_scope() + の最後に書かれている点である。つまり、最初に挙げたプリミティブ型の識別子は、「名前解決の最終段階で」、「他に同名の型が見つかっていなければ」プリミティブ型として解決される。 + + + 動作がわかったところで、例として次のコードを考える。 + + + + + + ここで main()boolstruct bool + として解決される。なぜなら、プリミティブ型の判定をする前に bool + という名前の別の型が見つかるからだ。 + +
+
+ まとめ + + Rust + のプリミティブ型は予約語ではない。名前解決の最終段階で特別扱いされ、他に同名の型が見つかっていなければ対応するプリミティブ型に解決される。 + +
+
diff --git a/vhosts/blog/content/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre.xml b/vhosts/blog/content/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre.xml new file mode 100644 index 00000000..ef17ae38 --- /dev/null +++ b/vhosts/blog/content/posts/2021-10-02/vim-difference-between-autocmd-bufwrite-and-bufwritepre.xml @@ -0,0 +1,165 @@ + +
+ + 【Vim】 autocmd events の BufWrite/BufWritePre の違い + + Vim の autocmd events における BufWrite/BufWritePre がどう違うのかを調べた結果、違いはないことがわかった。 + + + vim + + + + 2021-10-02 + Qiita から移植 + + + + + この記事は Qiita から移植してきたものです。 + 元 URL: https://qiita.com/nsfisis/items/79ab4db8564032de0b25 + +
+ TL; DR + + 違いはない。ただのエイリアス。 + +
+
+ 調査記録 + + Vim の autocmd events には似通った名前のものがいくつかある。大抵は + :help + に説明があるが、この記事のタイトルにある2つを含めた以下のイベントには、その違いについて説明がない。 + + + BufRead/BufReadPost + BufWrite/BufWritePre + BufAdd/BufCreate + + + このうち、BufAdd/BufCreate に関しては、:help BufCreate に + +
+ + The BufCreate event is for historic reasons. + +
+ + とあり、おそらくは BufAdd + のエイリアスであろうということがわかる。他の2組も同様ではないかと予想されるが、確認のため + vim と neovim のソースコードを調査した。 + +
+ + ソースコードへのリンク + vim (調査時点での master branch) + neovim (上に同じ) + +
+
+ vim のソースコード + + 以下は、autocmd events + の名前と内部で使われている整数値とのマッピングを定義している箇所である。見ての通り、上でエイリアスではないかと述べた3組には、それぞれ同じ内部値が使われている。 + + + https://github.com/vim/vim/blob/8e6be34338f13a6a625f19bcef82019c9adc65f2/src/autocmd.c#L85-L86 + + + + + + https://github.com/vim/vim/blob/8e6be34338f13a6a625f19bcef82019c9adc65f2/src/autocmd.c#L95-L97 + + + + + + https://github.com/vim/vim/blob/8e6be34338f13a6a625f19bcef82019c9adc65f2/src/autocmd.c#L103-L105 + + + + +
+
+ neovim のソースコード + + neovim の場合でも同様のマッピングが定義されているが、こちらの場合は Lua + で書かれている。以下にある通り、はっきり aliases と書かれている。 + + + https://github.com/neovim/neovim/blob/71d4f5851f068eeb432af34850dddda8cc1c71e3/src/nvim/auevents.lua#L119-L124 + + + + + + ところで、上では取り上げなかった FileEncoding だが、これは + :help FileEncoding にしっかりと書いてある。 + + + + +
+
+
+ まとめ + + 記事タイトルについて言えば、どちらも変わらないので好きな方を使えばよい。あえて言えば、次のようになるだろう。 + + + + BufAdd/BufCreate + + BufCreate は歴史的な理由により ("for historic reasons") 存在しているため、新しい方 (BufAdd) を使う + + + + BufRead/BufReadPost + + BufReadPre との対称性のため、あるいは BufWritePost との対称性のため BufReadPost を使う + + + + BufWrite/BufWritePre + + BufWritePost との対称性のため、あるいは BufReadPre との対称性のため BufWritePre を使う + + + + FileEncoding/EncodingChanged + + FileEncoding`Obsolete'' と明言されているので、`EncodingChanged を使う + + + + + ところでこの調査で知ったのだが、BufReadBufWrite + は上にある通り発火するタイミングが「後」と「前」で対称性がない。可能なら + Pre/Post 付きのものを使った方が分かりやすいだろう。 + +
+
diff --git a/vhosts/blog/content/posts/2021-10-02/vim-swap-order-of-selected-lines.xml b/vhosts/blog/content/posts/2021-10-02/vim-swap-order-of-selected-lines.xml new file mode 100644 index 00000000..c4c26ca8 --- /dev/null +++ b/vhosts/blog/content/posts/2021-10-02/vim-swap-order-of-selected-lines.xml @@ -0,0 +1,215 @@ + +
+ + Vimで選択した行の順番を入れ替える + + Vim で選択した行の順番を入れ替える方法。 + + + vim + + + + 2021-10-02 + Qiita から移植 + + + + + この記事は Qiita から移植してきたものです。 + 元 URL: https://qiita.com/nsfisis/items/4fefb361d9a693803520 + +
+ TL; DR + + ,g/^/m-1 + ]]> + +
+
+ バージョン情報 + + :version の一部 + +
+ + VIM - Vi IMproved 8.2 (2019 Dec 12, compiled Jan 26 2020 11:30:30) macOS + version Included patches: 1-148 Huge version without GUI. + +
+
+
+ よく紹介されている手法 +
+ <literal>tac</literal> / <literal>tail</literal> + + tactail -r などの外部コマンドを ! + を使って呼び出し、置き換える。 + +
+ + :h v_! + +
+ + tac コマンドや tail-r + オプションは環境によって利用できないことがあり、複数の環境を行き来する場合に採用しづらい + +
+
+ <literal>:g/^/m0</literal> + + こちらは外部コマンドに頼らず、Vim の機能のみを使う。g:global + コマンドの、m:move コマンドの略 + + + :global コマンドは :[range]global/{pattern}/[command] + のように使い、[range] で指定された範囲の行のうち、{pattern} + で指定された検索パターンにマッチする行に対して、順番に [command] + で指定された Ex コマンドを呼び出す。 + +
+ + :h :global + +
+ + :move コマンドは [range]:move {address} のように使い、[range] + で指定された範囲の行を {address} で指定された位置に移動させる。 + +
+ + :h :move + +
+ + :g/^/m0 のように組み合わせると、「すべての行を1行ずつ + 0行目(1行目の上)に動かす」という動きをする。これは確かに行の入れ替えになっている。 + + + なお、:g/^/m0 は全ての行を入れ替えるが、:N,Mg/^/mN-1 とすることで + N行目から + M行目を処理範囲とするよう拡張できる。手でこれを入力するわけにはいかないので、次のようなコマンドを用意する。 + + + ,g/^/m-1 + ]]> + + + これは望みの動作をするが、実際に実行してみると全行がハイライトされてしまう。次節で詳細を述べる。 + +
+
+
+ <literal>:g/^/m0</literal> の問題点 + + :global + コマンドは各行に対してマッチングを行う際、現在の検索パターンを上書きしてしまう。^ + は行の先頭にマッチするため、結果として全ての行がハイライトされてしまう。'hlsearch' + オプションを無効にしている場合その限りではないが、その場合でも直前の検索パターンが失われてしまうと + n コマンドなどの際に不便である。 + +
+ + :h @/ + +
+
+
+ 解決策 +
+ + [2020/9/28追記] より簡潔な方法を見つけたので次節に追記した + +
+ + 前述した :Reverse コマンドの定義を少し変えて、次のようにする: + + + reverse_lines(, ) + ]]> + + + 実行しているコマンドが変わったわけではないが、関数呼び出しを経由するようにした。これだけで前述の問題が解決する。 + + + この理由は、ユーザー定義関数を実行する際は検索パターンが一度保存され、実行が終了したあと復元されるため。結果として検索パターンが + ^ で上書きされることがなくなる。 + + + Vim のヘルプから該当箇所を引用する (強調は筆者による)。 + +
+ + :h autocmd-searchpat + + + Autocommands do not change the current search patterns. Vim saves the + current search patterns before executing autocommands then restores them + after the autocommands finish. This means that autocommands do not + affect the strings highlighted with the `hlsearch' option. + +
+ + これは autocommand + の実行に関しての記述だが、これと同じことがユーザー定義関数の実行時にも適用される。このことは + :nohlsearch のヘルプにある。同じく該当箇所を引用する + (強調は筆者による)。 + +
+ + :h :nohlsearch + + + (略) This command doesn’t work in an autocommand, because the + highlighting state is saved and restored when executing autocommands + |autocmd-searchpat|. Same thing for when invoking a user function. + +
+ + この仕様により、:g/^/m0 + の呼び出しをユーザー定義関数に切り出すことで上述の問題を解決できる。 + +
+
+ 解決策 (改訂版) +
+ + [2020/9/28追記] より簡潔な方法を見つけたため追記する + +
+ + ,g/^/m-1 + ]]> + + + まさにこのための Exコマンド、:keeppatterns + が存在する。:keeppatterns {command} + のように使い、読んで字の如く、後ろに続く + Exコマンドを「現在の検索パターンを保ったまま」実行する。はるかに分かりやすく意図を表現できる。 + +
+ + :h :keeppatterns + +
+
+
diff --git a/vhosts/blog/content/posts/2022-04-09/phperkaigi-2022-tokens.xml b/vhosts/blog/content/posts/2022-04-09/phperkaigi-2022-tokens.xml new file mode 100644 index 00000000..898d7ea9 --- /dev/null +++ b/vhosts/blog/content/posts/2022-04-09/phperkaigi-2022-tokens.xml @@ -0,0 +1,563 @@ + +
+ + PHPerKaigi 2022 トークン問題の解説 + + PHPerKaigi 2022 で私が作成した PHPer チャレンジ問題を解説する。 + + + conference + php + phperkaigi + + + + 2022-04-09 + 公開 + + + 2022-04-16 + 2問目、3問目の解説を追加、1問目に加筆 + + + +
+ はじめに + + 本日開始された PHPerKaigi 2022 の PHPer + チャレンジにおいて、弊社 + デジタルサーカス株式会社 の問題を + 3問作成した。この記事では、これらの問題の解説をおこなう。 + + + リポジトリはこちら: https://github.com/nsfisis/PHPerKaigi2022-tokens + +
+
+ 第1問 brainf_ck.php + + ソースコードはこちら。実行には PHP 8.1 以上が必要なので注意。 + + + (fn($x) => $f(fn(...$xs) => $x($x)(...$xs)))(fn($x) => $f(fn(...$xs) => $x($x)(...$xs))); + $id = \spl_object_id(...); + $put = fn($c) => \printf('%c', $c); + $mm = fn($p, $n) => new \ArrayObject(\array_fill(+!![], $n, $p)); + + $👉 = fn($m, $p, $b, $e, $mp, $pc) => [++$mp, ++$pc]; + $👈 = fn($m, $p, $b, $e, $mp, $pc) => [--$mp, ++$pc]; + $👍 = fn($m, $p, $b, $e, $mp, $pc) => [$mp, ++$pc, ++$m[$mp]]; + $👎 = fn($m, $p, $b, $e, $mp, $pc) => [$mp, ++$pc, --$m[$mp]]; + $📝 = fn($m, $p, $b, $e, $mp, $pc) => [$mp, ++$pc, $put($m[$mp])]; + $🤡 = fn($m, $p, $b, $e, $mp, $pc) => match ($m[$mp]) { + +!![] => [$mp, $z(fn($loop) => fn($pc, $n) => match ($id($p[$pc])) { + $b => $loop(++$pc, ++$n), + $e => $n === +!![] ? ++$pc : $loop(++$pc, --$n), + default => $loop(++$pc, $n), + })($pc, -![])], + default => [$mp, ++$pc], + }; + $🎪 = fn($m, $p, $b, $e, $mp, $pc) => match ($m[$mp]) { + +!![] => [$mp, ++$pc], + default => [$mp, $z(fn($loop) => fn($pc, $n) => match ($id($p[$pc])) { + $e => $loop(--$pc, ++$n), + $b => $n === +!![] ? $pc+![] : $loop(--$pc, --$n), + default => $loop(--$pc, $n), + })($pc, -![])], + }; + $🐘 = fn($p) => $z(fn($loop) => fn($m, $p, $b, $e, $mp, $pc) => + isset($p[$pc]) && $loop($m, $p, $b, $e, ...($p[$pc]($m, $p, $b, $e, $mp, $pc))) + )($mm(+!![], +(![].![])), $p, $id($🤡), $id($🎪), +!![], +!![]); + + $🐘([ + $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, + $🤡, + $👉, $👍, $👍, $👍, + $👉, $👍, $👍, $👍, $👍, $👍, + $👉, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, + $👉, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, $👍, + $👈, $👈, $👈, $👈, $👎, + $🎪, + $👉, $👍, $👍, $👍, $👍, $👍, $📝, + $👎, $👎, $📝, + $👉, $👎, $👎, $👎, $📝, + $👉, $👎, $👎, $👎, $📝, + $👎, $👎, $📝, + $👎, $📝, + $👈, $📝, + $👉, $👉, $👎, $👎, $📝, + $👍, $👍, $👍, $👍, $👍, $👍, $👍, $📝, + $👈, $👎, $👎, $👎, $👎, $📝, + $👈, $📝, + $👉, $👍, $👍, $📝, + $👉, $👎, $📝, + $👈, $📝, + ]); + ]]> + + + この問題は、単に適切なバージョンの PHP で動かせばトークンが得られる。 + +
+ 解説 +
+ 絵文字 + + まず目につくのは大量の絵文字だろう。 PHP + は識別子に使用できる文字の範囲が広く、絵文字も使うことができる。 + +
+
+ プログラム全体 + + Brainf*ck のインタプリタとプログラムになっている。 Brainf*ck + とは、難解プログラミング言語のひとつであり、ここで説明するよりも + Wikipedia の該当ページを読んだ方がよい。 + + + https://ja.wikipedia.org/wiki/Brainfuck + + + なお、brainf*ck プログラムを普通の書き方で書くと、次のようになる。 + + + + + + + > + + + + + + > + + + + + + + + + + + + + > + + + + + + + + + + + < < < < - + ] + > + + + + + . + - - . + > - - - . + > - - - . + - - . + - . + < . + > > - - . + + + + + + + + . + < - - - - . + < . + > + + . + > - . + < . + ]]> + + + 実行結果はこちら: https://ideone.com/22VWmb + + + それぞれの絵文字で表された関数が、各命令に対応している。 + + + $👉: > + $👈: < + $👍: + + $👎: - + $📝: . + $🤡: [ + $🎪: ] + + + , (入力) に対応する関数はない + (このプログラムでは使わないので用意していない)。 + + + なお、$🐘 はいわゆる main 関数であり、プログラムの実行部分である。 + +
+
+ 絵文字の選択 + + おおよそ意味に合致するよう選んでいるが、$🤡$🎪 + は弊社デジタルサーカスにちなんでいる。 また、$🐘 は PHP + のマスコットの象に由来する。 + +
+
+ strict_types + + declare 文の strict_types に指定できるのは、01 + の数値リテラルだが、 0x00b1 のような値も受け付ける。 今回は、PHP + 8.1 から追加された、0O または 0o から始まる八進数リテラルを使った。 + +
+
+ URL + + ソースコードのライセンスを示したこの部分だが、 + + + + + + 完全に合法な PHP のコードである。 https: 部分はラベル、// + 以降は行コメントになっている。 + +
+
+ リテラルなしで数値を生成する + + ソースコード中に、ほとんど数値リテラルが書かれていないことにお気づきだろうか。 + PHP では、型変換を利用することで任意の整数を作り出すことができる。 + + + + + + []! を適用すると true が返ってくる。それに + + を適用すると、bool から int ヘの型変換が走り、1 が生成される。10 + はさらにトリッキーだ。まず 10 を作り、. で文字列として結合する + ('10')。これに + を適用すると、string から int + への型変換が走り、10 が生まれる (コード量に頓着しないなら、1 を 10 + 個足し合わせてももちろん 10 が作れる)。 + + + また、error_reporting に指定しているのは -1 である。 これは、! + によって文字列を false にし、+ によって false0 + にし、さらにビット反転して -1 にしている。 + +
+
+ <literal>if</literal> 文なしで条件分岐 + + 三項演算子ないし match 式を使うことで、if + を一切書かずに条件分岐ができる。 また、&& / || も使えることがある。 + 遅延評価が不要なケースでは、[$t, $f][$cond] + のような形で分岐することもできる。 + +
+
+ <literal>while</literal>、<literal>for</literal> 文なしでループ + + 不動点コンビネータを使って無名再帰する + (詳しい説明は省略する。これらの単語で検索してほしい)。 ここでは、一般に + Z コンビネータとして知られるものを使った ($z)。 + + + 実際のところ、$🤡$🎪$🐘 は、一度 Scheme (Lisp の一種) + で書いてから PHP に翻訳する形で記述した。 + + + なお、PHP は末尾再帰の最適化をおこなわない (少なくとも今のところは) + ので、 あまりに長い brainf*ck + プログラムを書くとスタックオーバーフローする。 + +
+
+
+
+ 第2問 riddle.php + + ソースコードはこちら。実行には PHP 8.0 以上が必要なので注意。 + + + + + + さて、この問題はさきほどのように単純に実行しただけでは、謎のブロックが表示されるだけでトークンは得られない。 + トークンを得るためには、ソースコードを読み、定数 N + を特定する必要がある。 + + + ここでは、私の想定解を解説する。 + +
+ 読解 + + まずはソースコードを読んでいく。 + + + + + + 数値からなる $token があり、各要素をループしている。 + + + + + + まずは排他的論理和 (xor) を取り、 + + + + + + 二進数に変換して、 + + + + + + 0 を空白に、1 を # にし、 + + + + + + 5文字ごとに区切ったあと、改行で結合している。 + +
+
+ ヒント + + 次に、ソースコードに書いてあるヒントを読んでいく。 + + + N それ自体は、42 や 8128 といったような特別な意味を持たず、ランダムに決められている + $token の各要素は、1文字を表す + 1文字は 5x5 のセルからなる + 出力されるのは、完全な PHPer トークンである + + + ここで、PHPer トークンは必ず # 記号から始まることを思いだすと、 + $token の最初の数字 0x14B499C は、変換の結果 # + になるのではないかと予想される (なお、このことは、リポジトリの README + ファイルに追加ヒントとして書かれている)。 + +
+
+ 解く + + ここまでわかれば、あと一歩で解ける。すなわち、0x14B499C# + に変換されるような N を見つければよい。 + + + N は高々 + + + + + + なのでブルートフォースしてもよいが、ここではブルートフォースしない方法を紹介する。 + + + + + + この一連の変換に対する逆変換を考えると、次のようになる。 + + + + + + これを実行すると、N が得られる。 + +
+
+
+ 第3問 toquine.php + + ソースコードはこちら。 + + + vzcybqr(', ', $j), neenl_puhax(neenl_znc(sa($k) => fcevags('0k' . pue(37) . '07K', $k), $kf), 10)); + cevags($f, $g, fge_ebg13("<<<'Q'\a{$f}\aQ"), vzcybqr(",\a", $jf)); + Q; + $s = str_rot13($s); $xs = [ + 0x0AFABEA, 0x1F294A7, 0x1F2109F, 0x1F294A7, 0x0002800, 0x1F2109F, 0x0117041, 0x1F294A7, 0x1FAD6B5, 0x1F295B7, + 0x010FC21, 0x1FAD6B5, 0x1151151, 0x010FC21, 0x1F294A7, 0x1F295B7, 0x1FAD6B5, 0x1F294A7, 0x1F295B7, 0x1F8C63F, + 0x1F8C631, 0x1FAD6B5, 0x17AD6BD, 0x17AD6BD, 0x1F8C63F, 0x1F295B7, + ]; + $t = null.false; for ($i = 0; $i <= intdiv(__LINE__-035,6); ++$i) if (!isset($xs[$i])) break; else + $t .= implode("\n", str_split(str_replace(['0','1'], [' ','##'], sprintf(chr(37) . '025b', $xs[$i])), 012)) . "\n\n"; + $ws = array_map(fn($w) => implode(', ', $w), array_chunk(array_map(fn($x) => sprintf('0x' . chr(37) . '07X', $x), $xs), 10)); + printf($s, $t, str_rot13("<<<'D'\n{$s}\nD"), implode(",\n", $ws)); + ]]> + + + コメントにもあるとおり、次のようにして実行すれば答えがでてくる。 + + + + + + 実際にはもう少しパイプで繋げなければならない。 + +
+ 解説 +
+ プログラム全体 + + コメントにもあるとおり、これは quine (風) のプログラムになっている。 + Quine + とは、自分のソースコードをそっくりそのまま出力するようなプログラムのことである。 + + + このプログラムは、実行すると自身とほとんど同じプログラムを出力する。 + 異なるのはトークンになっている部分のみである。 + +
+
+ トークン + + $xs がトークンに対応している。変換のロジックは riddle.php + とほぼ同じなので省略する。 + +
+
+ 状態保持 + + トークンの何文字目まで出力したかを、ソースコードを変えずに (quine + なので) 覚えておく必要がある。 + このプログラムでは、トークンが出力されるとソースコードがだんだんと長くなっていくのを利用して、LINE + から情報を取得している。 + +
+
+ ROT 13 + + Quine は、素朴に書くとプログラムの一部が 2回記述されてしまう。 + これがあまり美しくないので、toquine.php では、ROT 13 + 変換を使って難読化した。 + + + それにしてもなぜこんなものが標準ライブラリに……。 + +
+
+
+
+ おわりに + + 解いていただいたみなさん、また、難易度調整につきあっていただいた社内のみなさん、ありがとうございました。 + + + 今回は直前に作りはじめたのもあり、3問だけかつ使い古されたネタばかりになってしまいましたが、 + 来年は 5問、より面白い問題を持っていきます。 + + + 実はもう作りはじめているので、どうか来年もありますように……。 + +
+
diff --git a/vhosts/blog/content/posts/2022-04-24/term-banner-write-tool-showing-banner-in-terminal.xml b/vhosts/blog/content/posts/2022-04-24/term-banner-write-tool-showing-banner-in-terminal.xml new file mode 100644 index 00000000..34082b12 --- /dev/null +++ b/vhosts/blog/content/posts/2022-04-24/term-banner-write-tool-showing-banner-in-terminal.xml @@ -0,0 +1,125 @@ + +
+ + term-banner: ターミナルにバナーを表示するツールを書いた + + ターミナルに任意の文字のバナーを表示するためのツールを Go で書いた。 + + + + 2022-04-24 + 公開 + + + 2022-04-27 + -f オプションについて追記 + + + +
+ はじめに + + こんなものを作った。 + + + + + + image::https://raw.githubusercontent.com/nsfisis/term-banner/main/screenshot.png[term-banner + のスクリーンショット] + + + コマンドライン引数として渡した文字列をターミナルに大きく表示する。 + + + リポジトリはこちら: https://github.com/nsfisis/term-banner + +
+
+ Motivation + + 以前、https://github.com/nsfisis/big-clock-mode[big-clock-mode] + という似たようなプログラムを書いた。 これは tmux の :clock-mode + コマンドに着想を得たもので、:clock-mode + よりも大きく現在時刻を表示する。 + + + big-clock-mode + を開発したのは、次のようなシチュエーションで使うためである。 + 弊社では現在リモートワークが基本だが、web + 会議などで画面共有しているときに、休憩を挟んで特定の時刻から再開する、ということがある。 + こういったケースで、画面上に現在の時刻を大きめに表示しておくと、モニタから離れても遠くから時刻がわかるので便利である。 + + + それこそタイマアプリか何かを使えばいいのだが、ターミナルに棲むいきものとしては、住処から離れたくないわけだ。 + + + しばらく便利に使っていたのだが、ひとつ不満点が出てきた。それは、再開する時刻がいつだったかを覚えておかなければならないということだ。 + どこかにメモしておいてもいいが、せっかくなら現在時刻とともに表示させておきたい。 + + + そんなわけで、「任意の文字列をターミナルに表示する」プログラムを書く運びとなった。 + まあ、作らなくても探せばあると思うが、作りたいものは作りたいので知ったことではない。 + +
+
+ プログラム + + 全体の流れは次のようになっている。 + + + フォントファイルを読み込む + コマンドライン引数を Shift-JIS に変換する (フォントが Shift-JIS 基準で並んでいるため) + 1文字ずつレンダリングしていく + + + big-clock-mode が Go 製なので、今回も Go で書いた。 PNG + が標準ライブラリにあったり、Shift-JIS + のエンコーディングが準標準ライブラリにあったりしたのは助かった。 + + + フォントファイルは go:embed + で実行ファイルに埋め込んでいるので、ビルド後はワンバイナリで動く。 + 仕事ではスクリプト言語ばかり書いているが、やはりコンパイル言語はいい。 + +
+
+ フォント + + フリーの 8x8 + ビットマップフォントである、https://littlelimit.net/misaki.htm[美咲フォント + 2021-05-05a 版] を使わせていただいた。 + + + はじめは自分でポチポチ打っていたのだが、「き」くらいまでやって挫折した。 + 同じく 8x8 + で作っていたのだが、平仮名でさえも、この小さなキャンバスにはとても収められない。 + + + 美咲フォントは、平仮名・片仮名に留まらず、JIS + 第一・第二水準の漢字までサポートしている。 + 第二水準ともなると一生お目にかかることのない字の方が多いくらいだが、これをこの大きさで書くというのは、もはや芸術の域である。 + + + さらに言うと、実のところ美咲フォントは実サイズ 7x7 + で作られており、余白が設けられている。 + これは、単純にそのまま並べても字間・行間を確保できるようにという配慮である。 + おかげでコーディングまで楽になった。 + + + ゴシック体と明朝体があったが、私の好みで明朝体の方にした。 + ただ、ゴシック体の方が見やすい気がするので、フォントを選べるように後ほど拡張するかもしれない。 + + + 2022-04-27 追記: -f オプションで選べるようにした。 + +
+
+ おわりに + + あなたもターミナルに住んでみませんか? + +
+
diff --git a/vhosts/blog/content/posts/2022-05-01/phperkaigi-2022.xml b/vhosts/blog/content/posts/2022-05-01/phperkaigi-2022.xml new file mode 100644 index 00000000..1c3b015d --- /dev/null +++ b/vhosts/blog/content/posts/2022-05-01/phperkaigi-2022.xml @@ -0,0 +1,171 @@ + +
+ + PHPerKaigi 2022 + + 2022-04-09 から 2022-04-11 にかけて開催された、PHPerKaigi 2022 に参加した。 + + + conference + php + phperkaigi + + + + 2022-05-01 + 公開 + + + +
+ はじめに + + 2022-04-09 から 2022-04-11 にかけて開催された、PHPerKaigi 2022 に、 + 一般参加者として参加した。 + 弊社デジタルサーカス株式会社はダイヤモンドスポンサーとなっており、 + スポンサー枠のチケットを使わせていただいた。 + + + 昨年のレポートはこちら。 + +
+
+ 感想 +
+ 厳選おすすめトーク + + 多くの素晴らしいトークの中から、特におすすめのものを 5つ選んだ。 + 是非聞いてほしい。引用部分は、リンク先プロポーザルから引用している。 + + + 予防に勝る防御なし - 堅牢なコードを導く様々な設計のヒント + +
+ + PHP はバージョンを追う毎に型宣言、例外、表明、列挙型などの機能が大幅に強化され、堅牢なコードを書くための機能が充実してきました。それらの機能はどう使うと効果的なのでしょうか。 + + + 本講演では PHP 8.1 をベースにして、誤りを想定してチェックするのではなく、そもそも誤りにくい設計とはどのようなものか、つまり「予防」の観点を軸足に、堅牢なコードを導くための様々な設計のヒントをご紹介します。 + +
+ + PHPのエラーを理解して適切なエラーハンドリングを学ぼう + +
+ + PHPを使ってるとよく遭遇する Fatal error / Parse error / Warning / Notice 理解していますか?
+ これらのエラー文を理解することで、すぐにエラーの原因に気付き適切に対象できる様になります!
+ またそれらを理解した上でのエラーハンドリングを学びましょう。 +
+
+ + エラー監視とテスト体制への改善作戦 + +
+ + 毎日流れてくるエラーに皆さんはどう向き合ってますか?
+ エラーを出さない事が一番ですが、完全に塞ぐ事は難しいと考えます。
+ サービス運用の中で本番環境から発生するエラー(サーバー・クライアントサイド・サードパーティ起因のエラー)への監視体制と、
+ エラー・バグ防御のためチームで行っているテストコード文化づくりの話をします。 +
+
+ + ISUCON11のPHP実装は、何を考え、どのようにして作られていたのか + +
+ + 昨年開催されたISUCON11にて問題(参考実装)のPHPへの移植を担当させていただきました。 + + + 最終的なソースコードこそシンプルなWebアプリケーションではありますが、その裏には
+ ・「(私の思う)良い設計」を実現するための意思決定
+ ・「ISUCONの問題」という位置付けに由来する取捨選択
+ ・移植中に遭遇したトラブルとその解決策
+ といった文脈や葛藤が存在しています。 +
+ + 本発表はそれらを共有することで
+ ・PHPアプリケーションの設計、実装事例として役立ててもらう
+ ・ISUCONの言語移植に興味を持ってもらう
+ ・ISUCON問題移植の「実装や設計の練習をする教材」としての可能性を知ってもらう
+ ことを目的とします。 +
+
+ + チームの仕事はまわっていたけど、メンバーはそれぞれモヤモヤを抱えていた話──40名の大規模開発チームで1on1ログを公開してみた + +
+ + サイボウズの大企業向けグループウェアのGaroon(ガルーン)は、PHPで開発されている20年目の製品です。ガルーン開発チームは日本で40名、ベトナムで50名の計90名ほどのチームになっています。また、コロナ禍でフルリモートでの活動がこの2年ほど継続してきました。 + + + フルリモートになっても仕事はまわっており、継続的にリリースはしていましたが、一方でお互いの考えていることや感じている問題意識が見えづらくなり、モヤモヤを抱えているメンバーが増えていました。 + + + このセッションでは、そういう状況で私がチーム外からジョインし、聴き役に徹しながら見える化することで状況を改善していった取り組みを紹介します。同じように大きなチームやリモートワークで難しさを感じている人に、難しさの原因への気づきや取り組みへのヒントがあれば幸いです。 + +
+
+
+ トークン問題の作成 + + 今回は、PHPer チャレンジ用に弊社のトークン問題を 3題作成した。 + こちらについては別途記事にしているので、そちらを参照されたい。 + +
+
+ PHPer チャレンジ + + 1位になった。
+ また、賞品として Echo Show 15 をいただいた。 +
+
+
+ カンファレンス全体への感想 + + 去年の参加レポ では、こんなことを書いた。 + +
+ + 1つ個人的な反省点としては、(中略) Discord しかりアンカンファレンスしかり「このイベントのこの瞬間にしかないコンテンツ」に触れずに、 + 後から見返せる発表やスライドに注力してしまった、ということだ。 + 発表の詳細な見直しはあとからできるのだから、今しかできないことを考えるべきだった。
+ まあ初カンファレンスだし、とお茶を濁しておこう。 +
+
+ + この反省を踏まえ、今年は積極的にほかの場 (公式の Discord サーバや、アンカンファレンス) にも参加した。
+ これにより、参加体験の質がはるかに向上した。特に Discord に関しては、登壇者ご本人による補足や、 + 質問への回答などがおこなわれる (ことが多い) ため、特別な理由のない限り、発言はしないまでも参加はしておいたほうが良いと思われる。 +
+ + なお、アンカンファレンスについては、1日目の終わりにトークン問題の解説放送もおこなった。 + + + また、今年はオフラインとオンラインのハイブリッド開催であったが、去年の全オンラインと比べて、オンライン参加の体験が落ちていなかったのは、特筆すべきであろう。 + 今年は 3回目のワクチン接種が間に合わなかったこともあり現地参加は見送ったのだが、来年は是非オフラインで参加したい。 + +
+
+
+ そして来年へ……? + + PHPerKaigi 2023 があるかどうか存じ上げないが、あるとすれば、次の 4つを目標としたい。 + + + プロポーザルを出す + PHPer チャレンジのトークン問題を 5題作成する + 現地に行く + PHPer チャレンジで圧勝する + + +
+
+ + 最後になりましたが、PHPerKaigi のスタッフ、スポンサー、スピーカーのみなさん、素敵な時間をありがとうございました。 + + + ではまた来年。 + +
+
diff --git a/vhosts/blog/content/posts/2022-08-27/php-conference-okinawa-code-golf.xml b/vhosts/blog/content/posts/2022-08-27/php-conference-okinawa-code-golf.xml new file mode 100644 index 00000000..ba5b7026 --- /dev/null +++ b/vhosts/blog/content/posts/2022-08-27/php-conference-okinawa-code-golf.xml @@ -0,0 +1,122 @@ + +
+ + PHP カンファレンス沖縄で出題されたコードゴルフの問題を解いてみた + + PHP カンファレンス沖縄の懇親会 LT で出題されたコードゴルフの問題を解いてみた。 + + + conference + php + phpcon + + + + 2022-08-27 + 公開 + + + +
+ はじめに + + 本日 PHP カンファレンス沖縄 2022 が開催された (らしい)。 + + + カンファレンスには参加できなかったものの、懇親会の LT で出題されたコードゴルフの問題が Twitter に流れてきたので、解いてみた。 + + + ツイート: https://twitter.com/m3m0r7/status/1563397620231712772 + スライド: https://speakerdeck.com/memory1994/php-conference-okinawa-2022-extra?slide=3 + +
+
+ + + 細かいレギュレーションは不明だったので、勝手に定めた。 + + + コマンドライン引数の第1引数で受けとる + 結果は標準出力に出す + コンマの直後にはスペースを1つ置く + 末尾コンマは禁止 + 数字でないものは入ってこないものとする + 負数は入ってこないものとする + + + 書いたものがこちら: + + + =$x;$n-=$x)$r[]=$x;echo implode(', ',$r??[]);?>] + ]]> + + + しめて 123 バイトとなった (末尾改行を含めずにカウント)。 + + + こちらは改行とスペースを追加したバージョン: + + + = $x; $n -= $x) + $r[] = $x; + echo implode(', ', $r ?? []); + + ?>] + ]]> + +
+
+ 使用したテクニック +
+ 指数表記 + + 割と多くの言語のゴルフで使えるテクニック。 + e を用いた指数表記で、大きな数を短く表す。 + このコードでは 10000500020001000 を指数表記している。 + +
+
+ foreach や for の中身を1つの文に + + foreachforif などの後ろには、 + 通常 { を続けて複数の文を連ねるが、中身の文を1つにしてしまえば、{} を省略できる。 + C言語などでも使える。 + +
+
+ $r に初期値を入れない + + PHP では、$r[] = ...... のような配列の末尾に追加する式を実行したとき、 + $r が未定義だった場合は $r を勝手に定義して空の配列で初期化してくれる。 + これを利用すると、$r = []; のような初期化が不要になる。 + + + ただし、プログラムに 0 が渡されるとループを一度も回らないので、$r が未定義になってしまい、 + implode() に渡すところでエラーになる。 + それを防ぐために $r ?? [] を使っている。 + + + もし 0 が渡されたケースを無視するなら、これが不要になるので 4 バイト縮む。 + +
+
+ PHP タグの外に文字列を置く + + PHP では、<?php ?> で囲われた部分の外側にある文字列は、そのまま出力される。 + 今回のケースでは、先頭と末尾に必ず [] を出力するので、そのまま書いてやればよい。 + +
+
+
+ おわりに + + 最後になりましたが、めもりーさん、楽しい問題をありがとうございました。 + +
+
diff --git a/vhosts/blog/content/posts/2022-08-31/support-for-communty-is-employee-benefits.xml b/vhosts/blog/content/posts/2022-08-31/support-for-communty-is-employee-benefits.xml new file mode 100644 index 00000000..eb9353d1 --- /dev/null +++ b/vhosts/blog/content/posts/2022-08-31/support-for-communty-is-employee-benefits.xml @@ -0,0 +1,72 @@ + +
+ + 弊社の PHP Foundation への寄付に寄せて + + 先日、私の勤めるデジタルサーカス株式会社が、PHP Foundation へ寄付をおこないました。 + 本件を社内でしつこく推進した1人として、推進の理由等を書き残しておきます。 + + + + 2022-08-31 + 公開 + + + +
+ はじめに + + 注: これは私個人の意見であり、所属する組織を代表するものではありません。 + + + 先日、私の勤める デジタルサーカス株式会社 が + PHP Foundation へ $2,000 の寄付をおこないました。 + + + 記事: https://www.dgcircus.com/news/581 + + + 本件を社内でしつこく推進した1人として、推進の理由等を書き残しておきます。 + +
+
+ なぜ? + + 組織としての寄付理由は前掲した記事に譲るとして、ここでは、私が社内でこの件を推進した理由について書くことにします。 + + + 当時の考えを端的にまとめた社内チャットの投稿があったので、それを引用します: + +
+ + 結局これを通したい (私の中での) 最大の理由が、「自分の勤める会社が、これをやる会社であってほしい」というのがあり、 + ↑にしても、感情ベースの理由しか出せていないというのが説得力に欠けている理由なのだと思いますが、 + 寄付の報告が流れてきたり、OSS のフリーライドの話が流れてきたりするたびに、自尊心が毀損される、というか + (これは大袈裟すぎる表現で、実際にはそこまで明確に傷ついているわけではありませんが)。 + + + 追記: 「肩身が狭くなる」というのがより適切でした。 + +
+ + ※文中の「↑にしても」は、ここに載せていない別の投稿を指しています。 + + + OSS を金銭的に支援したり、技術カンファレンスへ協賛したり + (あるいは CTO がカンファレンスを年2で主催したり: + iOSDC PHPerKaigi) + といった行為は、コミュニティへの貢献であると同時に、社員に対する精神的福利厚生でもあると言えるでしょう (知らんけど)。 + これらは、技術や技術者を大切にする組織である、ということの、対外的にも対内的にも強力なメッセージなのです。 + + + 以上が、私が社内で寄付の件を進めた (かなり私的な) 理由です。 + +
+
+ おわりに + + 最終的に社としての寄付まで漕ぎ着けられたのは、もちろん私の力ではなく役員の方々の決定によるものです。 + この場を借りて感謝申し上げます。 + +
+
diff --git a/vhosts/blog/content/posts/2022-09-29/write-fizzbuzz-in-php-2-letters-per-line.xml b/vhosts/blog/content/posts/2022-09-29/write-fizzbuzz-in-php-2-letters-per-line.xml new file mode 100644 index 00000000..88d71755 --- /dev/null +++ b/vhosts/blog/content/posts/2022-09-29/write-fizzbuzz-in-php-2-letters-per-line.xml @@ -0,0 +1,708 @@ + +
+ + 【PHP】 fizzbuzz を書く。1行あたり2文字で。 + + PHP で、fizzbuzz を書いた。ただし、1行あたりに使える文字数は2文字まで。 + + + php + + + + 2022-09-28 + 公開 + + + 2022-09-29 + 小さな文言の修正・変更 + + + +
+ 記事の構成について + + この記事は、普通の fizzbuzz を徐々に変形して最終形にしていく、という構成で書かれている。 + 最終形を見てどのような仕組みで動いているのか解読してから解説を読みたい、というかたがいれば、 + このページ + にソースコードがあるので、そちらを先に見てほしい。 + +
+
+ レギュレーション + + PHP で、次のような制約の下に fizzbuzz を書いた。 + + + + 1行あたりの文字数は2文字までに収めること (ただし <?php タグは除く) + + + 厳密な定義: <?php タグ以降のソースコードが、2 byte ごとにラインフィード (LF) で区切られること + + + + スペースやタブを使用しないこと + + ループのアンロールをしないこと + + 100 回ループの代わりに 100 回コードをコピペ、というのは禁止 + + + PHP 7.4〜8.1 で動作すること + 実行時に Notice や Warning が出ないこと + 標準的なインストール構成の PHP で実現できること (デフォルトで有効になっていない拡張等を使わないこと) + + + 備考: PHP には short_open_tag というオプションがあり、 + これを有効にするとファイル冒頭の <?php の代わりに <? + を使うことができ、文字どおり1行2文字で書ける。 + ただ、このオプションはデフォルト off になっている環境が多いようなので、今回は使わないことにした。 + +
+
+ 主な障害 + + 1行あたりの文字数など、適当に改行を挟めばいいだけではないのか? + + + 特に、C言語でこのような試みをおこなったことがあるかたならそう思うだろう。事実、Cでのこの制約はほとんど無意味に等しい。 + + + \ + /* + */ + i\ + n\ + t\ + /* + */ + m\ + a\ + i\ + n( + ){ + f\ + o\ + r( + i\ + n\ + t\ + /* + */ + i= + 1; + i< + 1\ + 0\ + 0; + i\ + +\ + +) + if + (i + %\ + 15 + == + 0) + p\ + r\ + i\ + n\ + t\ + f( + "\ + F\ + i\ + z\ + z\ + B\ + u\ + z\ + z\ + %\ + c\ + ", + 10 + ); + + /* あとは同じように普通のプログラムを変形するだけなので省略 */ + ]]> + + + バックスラッシュを使った行継続がトークンを区切らない、というのがポイントだ。 + + + さて、PHP ではそもそもバックスラッシュを行継続に使うことができない。 + これにより、「3文字以上からなるトークンが一切使えない」という制約が課される。 + 例えば、echo で出力することや、for でループすること、 + new でインスタンスを生成することができない。 + 特に、出力は fizzbuzz をどんなアルゴリズムで実装しようとおこなわなければならないので、できないのは致命的である。 + + + 当然、名前が3文字以上ある関数も使えない。なお、標準 PHP の範囲内において、名前が 2文字以下の関数は以下のとおりである: + + + + _: gettext のエイリアス + + + dl: 拡張モジュールをロードする + + + pi: 円周率を返す + + + + (環境によって多少は変わるかも) + + + 2文字の関数を定義しまくった拡張モジュールを用意しておいて dl() で読み込む行為は、レギュレーションで定めた + +
+ + 標準的なインストール構成の PHP で実現できること (デフォルトで有効になっていない拡張等を使わないこと) + +
+ + に反する (というより、「それだとおもしろくもなんともないので、このルールを足した」というのが正しい)。 + + + また、2文字だと文字列がまともに書けないのも辛い。'' だけで2文字使うので、 + 「1文字の文字列リテラル」というものを書くことができない。PHP では文字列リテラル中に生の改行が書けるので + + + + + + とすると $a"\na" になるのだが、余計な改行が入ってしまう。 + + + これらの障害をどのように乗り越えるのか、次節から見ていく。 + +
+
+ 解説 +
+ 普通の (?) fizzbuzz + + まずは普通に書くとしよう。 + + + + + + 素直に書いた fizzbuzz とは言い難いが、このくらいは普通だということにしておかないと、この先がやっていられないので許してほしい。 + +
+
+ <literal>for</literal> の排除 + + for は、3文字もある長いキーワードである。 + こんなものは使えない。array_ 系の関数を使って、適当に置き換えるとしよう。 + + + + printf((($i % 3 ? '' : 'Fizz') . ($i % 5 ? '' : 'Buzz') ?: $i) . "\n"), + ); + ]]> + + + array_walkrangeprintf といった + for よりも長いトークンが現れてしまったが、これは次節で直すことにする。 + なお、echo は文 (statement) であり式 (expression) ではないので、式である printf に置き換えた。 + +
+
+ 関数呼び出しの短縮 + + rangearray_walkprintf は長すぎるのでどうにかせねばならない。 + ここで、PHP の可変関数を使う。可変関数とは、関数名が文字列として入った変数を経由して、関数を呼び出す機能である。 + + + + $p((($i % 3 ? '' : 'Fizz') . ($i % 5 ? '' : 'Buzz') ?: $i) . "\n"), + ); + ]]> + + + これで関数を呼び出している所は短くなった。 + では、$r$w$p、 + また 'Fizz''Buzz' はどうやって 1 行 2 文字に収めるのか。 + 次のテクニックへ移ろう。 + +
+
+ 余談: PHP 8.x で動作しなくてもいいなら + + 今回使ったテクニックを説明する前に、余談として、文字列リテラルの短縮法として今回採用しなかったものを紹介する。 + +
+ + PHP 7.4〜8.1 で動作すること + +
+ + というルールがない場合、「未定義の定数が評価された場合、その定数の名前が値になる」という PHP 7.x までの仕様が利用できる。 + 例えば、 Fizz という文字列が欲しければ、次のようにする。 + + + + + + こうして簡単に文字列を作れる。 + なお、この仕様は 7.x 時点でも警告を受けるので、@ 演算子を使って抑制してやるとよい。 + + + + + + むしろ、このことがわかっていたからこそ PHP 8.x での動作を要件に課したところがある。 + +
+
+ 文字列リテラルの短縮 + + 実際に使った手法の説明に移る。 + + + ずばり、文字列同士のビット演算を使う。 + PHP では、文字列同士でビット演算 (&|^) をした場合、 + 文字列の各バイトごとに指定したビット演算がなされ、それを結合したものが演算結果となる。 + + + F]AXQ + ]]> + + + これを踏まえ、次のコードを見てみよう。 + + + + + + 実行すると、range が表示される。 + さて、PHP では文字列リテラル中に生の改行を直接書いてもよいのだった (「主な障害」の節を参照のこと)。 + 書きかえてみよう。 + + + + + + さらに # を使って適当に調整すると、次のようになる。 + + + + + + 1行あたり2文字で、range という文字列を生成することに成功した。 + 他の必要な文字列にも、同様の処理をほどこす。 + + + 備考: Buzz 中にある小文字の u は、このロジックだと non-printable な文字になってしまう。 + ここまでのテクニックを駆使すれば回避するのはそう難しくないので、考えてみてほしい。 + +
+
+
+ 完成系 + + 完成したものがこちら。 + + + + $p + (( + (# + $i + %3 + ?# + '' + :# + $f + ). + (# + $i + %5 + ?# + '' + :# + $b + )? + :# + $i + )# + .' + ') + ); + ]]> + +
+
+ 感想など + + PHP は、スクリプト言語の中だとシンタックスシュガーが少ない (体感)。 + この挑戦は不可能に思われたが、PHP マニュアルとにらめっこしていたらなんとかなった。 + + + みんなもプログラムを細長くしよう。 + +
+
+ 余談2: 別解 + + PHP では、バッククォートを使ってシェルを呼び出せる。 + これは shell_exec 関数と等価である。 + さて、PHP ではバックスラッシュによる行継続が使えないと書いたが、シェルでは使える + (当然だが、呼び出されるシェルに依存する。Bash なら大丈夫だろう。知らんけど)。 + + + + + + なお、ここでは簡単のため出力に printf をそのまま使っているが、 + 実際には printf という文字列を合成して可変関数で呼び出す。 + + + ただし、これでは + +
+ + スペースやタブを使用しないこと + +
+ + に違反してしまう。スペースが使えないと引数とコマンドを区切れない。これは困った。 + + + もうこれ以上は不可能だと思っていたのだが、この記事の執筆中に解決する方法を思いついたので載せておく。 + + + + + + 先程と同じく、chrprintf を生成する部分は長くなるので省いた。 + + + + + + は変数で、中にはスペースとエスケープが入っている (chr(32) . chr(92))。 + シェルに渡されている文字列は次のようになる。 + + + + + + これは、前掲したコマンドと同じだ。 + かくして、スペースを陽に書かずにシェルをおおよそ自由に扱えるようになった。 + Fizzbuzz のワンライナーくらいすぐ書けるだろうから、あとはなんとかなるだろう (試してないけど)。 + + + ということでこれは別解ということにしておく。 + + + ちなみに、PHP 8.2 からは、この記法で Warning が出るようになるようだ。 + + + + + + 最新版で警告が出るというのも美しくないので、私としては本編の解法を推す。 + +
+
diff --git a/vhosts/blog/content/posts/2022-10-23/phperkaigi-2023-unused-token-quiz-1.xml b/vhosts/blog/content/posts/2022-10-23/phperkaigi-2023-unused-token-quiz-1.xml new file mode 100644 index 00000000..79202320 --- /dev/null +++ b/vhosts/blog/content/posts/2022-10-23/phperkaigi-2023-unused-token-quiz-1.xml @@ -0,0 +1,188 @@ + +
+ + PHPerKaigi 2023: ボツになったトークン問題 その 1 + + 来年の PHPerKaigi 2023 でデジタルサーカス株式会社から出題予定のトークン問題のうち、ボツになった問題を公開する (その 1)。 + + + php + phperkaigi + + + + 2022-10-23 + 公開 + + + +
+ はじめに + + 2023 年 3 月 23 日から 25 日にかけて開催予定 (記事執筆時点) の、 + PHPerKaigi 2023 において、 + 昨年と同様に、弊社 デジタルサーカス株式会社 から、 + トークン問題を出題予定である。 + + + 昨年のトークン問題の記事はこちら: PHPerKaigi 2022 トークン問題の解説 + + + すでに 2023 年用の問題は作成済みであるが、その制作過程の中でいくつかボツ問ができた。 + せっかくなので、PHPerKaigi 開催を待つ間に紹介しようと思う。 + + + 10 月から 2 月まで、毎月 1 記事ずつ公開していく予定 (忘れていなければ)。 + +
+
+ 問題 + + 注意: これはボツ問なので、得られたトークンを PHPerKaigi で入力してもポイントにはならない。 + + + + +
+
+ トークン入手方法 + + ソースを見るとわかるとおり、$argv[1] を参照している。 + それを なる変数に代入しているので、円周率を渡してみる。 + + + + + + 失敗してしまった。精度を上げてみる。 + + + + + + だめだった。これを成功するまで繰り返す。 + + + 最初にトークンが得られるのは、小数点以下 16 桁目まで入力したときで、こうなる。 + + + + + + めでたくトークン「#YO」が手に入った。 + +
+
+ 解説 + + 短いので頭から追っていく。 + + + + + + 入力のバリデーション部分。数値のみ受け付ける。 + + + + + + を 2 文字ごとに区切り (str_split)、 + 数値を ASCII コードと見做して文字に変換 (chr) して結合 (implode) している。 + + + 例えば、'656667' だったとすると、 + 656667 に対応した + 'A''B''C' へと変換され、'ABC' になる。 + + + ABC + ]]> + + + + + + 正規表現でマッチングしている。\x23# と同じであることに留意すると、 + この正規表現は「# から始まる 2 以上の長さ (含 #) の文字列で、 + 最初に現れるスペースまで」にマッチする。つまりこれは、PHPerKaigi におけるトークンである。 + + + なお、# を直接書いていないのは、/#.+?) / と書くと、 + #.+?) という意図せぬトークンが登録されてしまうからである。 + + + + + + 最後にトークンのハッシュ値を見て、想定解かどうかを確認する。 + +
+
+ おわりに + + 円周率を何桁も計算して ASCII コード経由で文字列化すれば、トークンっぽいものがどこかで出てくるのではないか、と考えて生まれた作品。 + + + 最初は真面目に円周率の計算プログラムを組んでいたのだが、いざ動かしてみるとやけに浅いところにあったので驚いた + (ちなみに、それでも M_PIpi() では精度が足りない)。 + 見つけたときは狂喜したものの、冷静になってみると大して面白くなかったのでボツになった。 + むしろ、100 万桁目くらいに埋まっていてくれたほうがよかったかもしれない。 + +
+
diff --git a/vhosts/blog/content/posts/2022-10-28/setup-server-for-this-site.xml b/vhosts/blog/content/posts/2022-10-28/setup-server-for-this-site.xml new file mode 100644 index 00000000..b0ad60e0 --- /dev/null +++ b/vhosts/blog/content/posts/2022-10-28/setup-server-for-this-site.xml @@ -0,0 +1,313 @@ + +
+ + 【備忘録】 このサイト用の VPS をセットアップしたときのメモ + + GitHub Pages でホストしていたこのサイトを VPS へ移行したので、そのときにやったことのメモ。99 % 自分用。 + + + note-to-self + + + + 2022-10-28 + 公開 + + + 2023-08-30 + ssh_config に IdentitiesOnly yes を追加 + + + +
+ はじめに + + これまでこの blog は GitHub Pages でホストしていたのだが、先日 VPS に移行した。 + そのときにおこなったサーバのセットアップ作業を書き残しておく。 + 99 % 自分用の備忘録。別のベンダに移したりしたくなったら見に来る。 + + + 未来の自分へ: 特に自動化してないので、せいぜい苦しんでくれ。 + +
+
+ VPS + + さくらの VPS の 2 GB プラン。 + そこまで真面目に選定していないので、困ったら移動するかも。 + +
+
+ 事前準備 +
+ サーバのホスト名を決める + + モチベーションが上がるという効能がある。今回は藤原定家から取って teika にした。 + たいていいつも源氏物語の帖か小倉百人一首の歌人から選んでいる。 + +
+
+ SSH の鍵生成 + + ローカルマシンで鍵を生成する。 + + + + + + teika.key はローカルからサーバへの接続用、github2teika.key は、 + GitHub Actions からサーバへのデプロイ用。 + +
+
+ SSH の設定 + + .ssh/config に設定しておく。 + + + + +
+
+
+ 基本のセットアップ +
+ SSH 接続 + + VPS 契約時に設定した管理者ユーザとパスワードを使ってログインする。 + +
+
+ ユーザを作成する + + 管理者ユーザで作業すると危ないので、メインで使うユーザを作成する。 + sudo グループに追加して sudo できるようにし、su で切り替え。 + + + + +
+
+ ホスト名を変える + + + +
+
+ 公開鍵を置く + + + + + authorized_keys には、ローカルで生成した ~/.ssh/teika.key.pub と + ~/.ssh/github2teika.key.pub の内容をコピーする。 + +
+
+ SSH の設定 + + SSH の設定を変更し、少しでも安全にしておく。 + + + + + + Port を変更 + PermitRootLoginno + PasswordAuthenticationno + + + そして設定を反映。 + + + + +
+
+ SSH で接続確認 + + 今の SSH セッションは閉じずに、ターミナルを別途開いて疎通確認する。 + セッションを閉じてしまうと、SSH の設定に不備があった場合に締め出しをくらう。 + + + + +
+
+ ポートの遮断 + + デフォルトの 22 番を閉じ、設定したポートだけ空ける。 + + + + + + ここでもう一度 SSH の接続確認を挟む。 + +
+
+ GitHub 用の SSH 鍵 + + GitHub に置いてある private リポジトリをサーバから clone したいので、SSH 鍵を生成して置いておく。 + + + + + + GitHub の設定画面 から、この公開鍵を追加する。 + + + + + + 設定はこう。 + + + + + + 最後に接続できるか確認しておく。 + + + + +
+
+ パッケージの更新 + + + +
+
+
+ サイトホスティング用のセットアップ +
+ DNS に IP アドレスを登録する + + このサーバは固定の IP アドレスがあるので、A レコードに直接入れるだけで済んだ。 + +
+
+ 使うソフトウェアのインストール + + + +
+
+ メインユーザが Docker を使えるように + + + +
+
+ HTTP/HTTPS を通す + + 80 番と 443 番を空ける。 + + + + +
+
+ リポジトリのクローン + + + +
+
+ certbot で証明書取得 + + + +
+
+ サーバを稼動させる + + + +
+
+
+ 感想 + + (業務でなく) 個人だと数年ぶりのサーバセットアップで、これだけでも割と時間を食ってしまった。 + とはいえ式年遷宮は楽しいので、これからも定期的にやっていきたい。 + コンテナデプロイにしたい気持ちもあるのだが、色々実験したい関係上、本物のサーバも欲しくはある。 + 次の式年遷宮では、手順の一部だけでも自動化したいところ。 + +
+
diff --git a/vhosts/blog/content/posts/2022-11-19/phperkaigi-2023-unused-token-quiz-2.xml b/vhosts/blog/content/posts/2022-11-19/phperkaigi-2023-unused-token-quiz-2.xml new file mode 100644 index 00000000..0332179d --- /dev/null +++ b/vhosts/blog/content/posts/2022-11-19/phperkaigi-2023-unused-token-quiz-2.xml @@ -0,0 +1,174 @@ + +
+ + PHPerKaigi 2023: ボツになったトークン問題 その 2 + + 来年の PHPerKaigi 2023 でデジタルサーカス株式会社から出題予定のトークン問題のうち、ボツになった問題を公開する (その 2)。 + + + php + phperkaigi + + + + 2022-11-19 + 公開 + + + +
+ はじめに + + 2023 年 3 月 23 日から 25 日にかけて開催予定 (記事執筆時点) の PHPerKaigi 2023 において、 + 昨年と同様に、弊社 デジタルサーカス株式会社 からトークン問題を出題予定である。 + + + 昨年のトークン問題の記事はこちら: PHPerKaigi 2022 トークン問題の解説 + + + すでに 2023 年用の問題は作成済みであるが、その制作過程の中でいくつかボツ問ができた。せっかくなので、PHPerKaigi 開催を待つ間に紹介しようと思う。 + + + 10 月から 2 月まで、毎月 1 記事ずつ公開していく予定 (忘れていなければ)。 + + + その 1 はこちら: PHPerKaigi 2023: ボツになったトークン問題 その 1 + +
+
+ 問題 + + 注意: これはボツ問なので、得られたトークンを PHPerKaigi で入力してもポイントにはならない。 + + + trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s='​trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?> + trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s='​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?> + trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s='​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?> + trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s='​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?> + trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s='​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?> + trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s='​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?> + trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s='​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?> + trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s='​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?> + trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s='​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?> + trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s='​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?> + ]]> + + + "And Then There Were None" (そして誰もいなくなった) と名付けた作品。変則 quine (自分自身と同じソースコードを出力するプログラム) になっている。 + +
+
+ トークン入手方法 + + 実行してみると、次のような出力が得られる。 + + + trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s='​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?> + trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s='​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?> + trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s='​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?> + trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s='​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?> + trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s='​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?> + trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s='​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?> + trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s='​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?> + trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s='​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?> + trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s='​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?> + ]]> + + + 1 行目を除き、先ほどのコードとほぼ同じものが出てきた。もう一度実行してみる。 + + + trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s='​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?> + trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s='​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?> + trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s='​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?> + trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s='​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?> + trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s='​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?> + trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s='​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?> + trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s='​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?> + trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s='​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​trim($s,"​"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?> + ]]> + + + 今度は 2 行目が書き換えられた。すべての行が変化するまで繰り返すと次のようになる。 + + + + + + トークン「#WELOVEPHP」が手に入った。 + +
+
+ 解説 + + 一見すると同じ行が 10 行並んでいるだけなのにも関わらず、なぜそれぞれの行で出力が変わるのか。ソースコードをコピーして、適当なエディタに貼り付けるとわかりやすい。 + + + Vim で開くと次のようになる (1 行目を抜粋)。 + + + trim($s,"<200b>"):fn($s)=>chr(strlen($s)/3))($s='<200b>trim($s,"<200b>"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>')."\n","\x27$s\x27");?> + ]]> + + + <200b> と表示されているのは、Unicode の U+200b で、ゼロ幅スペースである。 + + + + エディタによっては、ゼロ幅スペースが見えないことがある。VSCode ではブラウザと同様に不可視だった。 + + + + 文字列リテラルの中にゼロ幅スペースを仕込むことで、見た目を変えずに情報をエンコードすることが可能となる。 + + + 続いて、トークンへの変換ロジックを解析する。注目すべきはこの部分だ。以下、ゼロ幅スペースは Vim での表示に合わせて <200b> と記載する。 + + + chr(strlen($s)/3) + ]]> + + + PHP の strlen() は文字列のバイト数を返す。1 行目の $s は以下の内容となっており、 + + + trim($s,"<200b>"):fn($s)=>chr(strlen($s)/3))($s=%s)."\n","\x27$s\x27");?>' + ]]> + + + このソースコードは UTF-8 で書かれているので、105 バイトになる。それを 3 で割ると 35 となり、これは # の ASCII コードと一致する。他の行も、同様にしてゼロ幅スペースを詰めることで文字列長を調整し、トークンをエンコードしている。 + + + デコード部以外の部分は、quine のための記述である。 + +
+
+ おわりに + + CVE-2021-42574 に着想を得た作品。この脆弱性は、Unicode の制御文字である left-to-right mark と right-to-left mark を利用し、ソースコードの実際の内容を欺く、というもの。簡単のためゼロ幅スペースを用いることとし、ついでに quine にもするとこうなった。 + + + ボツになった理由は、ゼロ幅スペースを表示してくるエディタが想像以上に多かったため。「同じ行が並んでいるだけなのに出力が異なる」というアイデアの根幹を崩されてしまうので、この問題は不採用となった。 + +
+
diff --git a/vhosts/blog/content/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3.xml b/vhosts/blog/content/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3.xml new file mode 100644 index 00000000..90838852 --- /dev/null +++ b/vhosts/blog/content/posts/2023-01-10/phperkaigi-2023-unused-token-quiz-3.xml @@ -0,0 +1,329 @@ + +
+ + PHPerKaigi 2023: ボツになったトークン問題 その 3 + + 来年の PHPerKaigi 2023 でデジタルサーカス株式会社から出題予定のトークン問題のうち、ボツになった問題を公開する (その 3)。 + + + php + phperkaigi + + + + 2023-01-10 + 公開 + + + +
+ はじめに + + 2023 年 3 月 23 日から 25 日にかけて開催予定 (記事執筆時点) の PHPerKaigi 2023 において、 + 昨年と同様に、弊社 デジタルサーカス株式会社 からトークン問題を出題予定である。 + + + 昨年のトークン問題の記事はこちら: PHPerKaigi 2022 トークン問題の解説 + + + すでに 2023 年用の問題は作成済みであるが、その制作過程の中でいくつかボツ問ができた。 + せっかくなので、PHPerKaigi 開催を待つ間に紹介しようと思う。 + + + 10 月から 2 月まで、毎月 1 記事ずつ公開していく予定 (忘れていなければ → 忘れていたので 12 月公開予定だった記事を今書いている)。 + + + その 1 はこちら: PHPerKaigi 2023: ボツになったトークン問題 その 1 + その 2 はこちら: PHPerKaigi 2023: ボツになったトークン問題 その 2 + +
+
+ 問題 + + 注意: これはボツ問なので、得られたトークンを PHPerKaigi で入力してもポイントにはならない。 + + + getPrevious()) printf('%c', $e->getLine() + 23); + echo "\n"; + } + function f(int $i) { + if ($i < 0) f(); + try { + match ($i) { + 0 => 0 / 0, + + + + 15, 36 => 0 / 0, + 14 => 0 / 0, + 37 => 0 / 0, + + + + + + + + + + + 6 => 0 / 0, + + 5 => 0 / 0, + + 22 => 0 / 0, + + + + + 34, 35 => 0 / 0, + + + + + + + + + 25 => 0 / 0, + 17, 21 => 0 / 0, + + 24, 32 => 0 / 0, + + + + + + + + 33 => 0 / 0, + + 16 => 0 / 0, + + + 18 => 0 / 0, + + + + + + + + + 7 => 0 / 0, + + 2 => 0 / 0, + 1, 20 => 0 / 0, + 10, 28 => 0 / 0, + 8, 12, 26 => 0 / 0, + 4, 9, 13 => 0 / 0, + + + + + + 31 => 0 / 0, + + 29 => 0 / 0, + + 11 => 0 / 0, + + + + 3, 19, 23 => 0 / 0, + + + 27 => 0 / 0, + + 30 => 0 / 0, + }; + } finally { + f($i - 1); + } + } + + + + + + + + function g() { + return __LINE__; + } + ]]> + + + "Catchline" と名付けた作品。実行するとトークン #base64_decode('SGVsbG8sIFdvcmxkIQ==') が得られる。 + + + トークンは PHP の式になっていて、評価すると Hello, World! という文字列になる。PHPer チャレンジのトークンには空白を含められないという制約があるが、こういった形でトークンにすれば回避できる。 + +
+
+ 解説 +
+ 概要 + + 例外が発生した行数にデータをエンコードし、それを catch で捕まえて表示している。 + +
+
+ 例外オブジェクトの連鎖 + + ExceptionError には $previous というプロパティがあり、コンストラクタの第3引数から渡すことができる。主に 2つの用法がある: + + + エラーを処理している途中に起こった別のエラーに、元のエラー情報を含める + 内部エラーをラップして作られたエラーに、内部エラーの情報を含める + + + このうち 1つ目のケースは、 finally 節の中でエラーを投げると PHP 処理系が勝手に $previous を設定してくれる。 + + + getMessage() . PHP_EOL; + // => Error 2 + echo $e->getPrevious()->getMessage() . PHP_EOL; + // => Error 1 + } + ]]> + + + この知識を元に、トークンの出力部を解析してみる。 + +
+
+ 出力部の解析 + + 出力部をコメントや改行を追加して再掲する: + + + getPrevious()) { + printf('%c', $e->getLine() + 23); + } + echo "\n"; + } + ]]> + + + 出力をおこなう catch 節を見てみると、 Throwable::getPrevious() を呼び出してエラーチェインを辿り、 Throwable::getLine() でエラーが発生した行数を取得している。その行数に 23 なるマジックナンバーを足し、フォーマット指定子 %c で出力している。 + + + フォーマット指定子 %c は、整数を ASCII コードRAS syndrome と見做して印字する。トークン #base64_decode('SGVsbG8sIFdvcmxkIQ==')b であれば、ASCII コード 98 なので、75 行目で発生したエラー、 + + + 0 / 0, + ]]> + + + によって表現されている。エラーを起こす方法はいろいろと考えられるが、今回はゼロ除算を使った。 + + + それでは、エラーチェインを作る箇所、関数 f() を見ていく。 + +
+
+ データ構成部の解析 + + f() の定義を再掲する (エラーオブジェクトの行数を利用しているので、一部分だけ抜き出すと値が変わることに注意): + + + 0 / 0, // 12 行目 + + + + 15, 36 => 0 / 0, + 14 => 0 / 0, + 37 => 0 / 0, + + // (略) + + 30 => 0 / 0, // 97 行目 + }; + } finally { + f($i - 1); + } + } + ]]> + + + 前述のように、 finally 節でエラーを投げると PHP 処理系が $previous を設定する。ここでは、エラーを繋げるために f() を再帰呼び出ししている。最初に f() を呼び出している箇所を確認すると、 + + + + + + + + + f() には 111 / 337 が渡されることがわかる。そこから 1 ずつ減らして再帰呼び出ししていき、0 より小さくなったら f() を引数なしで呼び出す。引数の数が足りないと呼び出しに失敗するので、再帰はここで止まる。 + + + エラーチェインは、最後に発生したエラーを先頭とした単方向連結リストになっているので、順に + + + f() の引数が足りないことによる呼び出し失敗 + f(0) の呼び出しで発生したゼロ除算 + f(1) の呼び出しで発生したゼロ除算 + + f(37) の呼び出しで発生したゼロ除算 + + + となっている。出力の際は catch したエラーの getPrevious() から処理を始めるので、1 番目の f() によるエラーは無視され、 f(0) によるエラー、 f(1) によるエラー、 f(2) によるエラー、と出力が進む。 + + + f()0 を渡したときは 12 行目にある match0 でゼロ除算が起こるので、行数が 12 となったエラーが投げられる。出力部ではこれに 23 を足した数を ASCII コードとして表示しているのだった。 12 + 2335、ASCII コードでは # である。これがトークンの 1文字目にあたる。 + +
+
+
+ おわりに + + 「行数」というのはトークン文字列をデコードする対象として優れている。 + + + トークンの一部や全部が陽に現れない + __LINE__ で容易に取得できる + + + しかし、こういった「変な」プログラムを何度も読んだり書いたりしていると、 __LINE__ を使うのはあまりにありきたりで退屈になる。では、他に行数を取得する手段はないか。こうして Throwable を思いつき、続けてエラーオブジェクトには $previous があることを思い出した。 + + + 今回エラーを投げるのにゼロ除算を用いたのは、それがエラーを投げる最も短いコードだと考えたからである。もし 3バイト未満で Throwable なオブジェクトを投げる手段をご存じのかたがいらっしゃれば、ぜひご教示いただきたい。……と締める予定だったのだが、0/0 のところを存在しない定数にすれば、簡単に 1バイトを達成できた。ゼロ除算している箇所はちょうど 26 箇所あるので、アルファベットにでもしておけば意味ありげで良かったかもしれない。 + +
+
diff --git a/vhosts/blog/content/posts/2023-03-10/rewrite-this-blog-generator.xml b/vhosts/blog/content/posts/2023-03-10/rewrite-this-blog-generator.xml new file mode 100644 index 00000000..9deff792 --- /dev/null +++ b/vhosts/blog/content/posts/2023-03-10/rewrite-this-blog-generator.xml @@ -0,0 +1,97 @@ + +
+ + このブログのジェネレータを書き直した + + このブログのジェネレータを書き直したので、やったことを書き記しておく。 + + + + 2023-03-10 + 公開 + + + +
+ はじめに + + このブログを構築するシステムを書き直したのは 2度目である。 + 元々立ち上げた当初は、静的サイトジェネレータである Hugo を使っていた。 + それを Asciidoctor にいくつかのカスタムを加えた自前のジェネレータに移行したのが 2022年の11月ごろだ。 + そして今回、スクラッチから書いた Deno 製のジェネレータに移行した。 + + + この記事では、移行の理由などを (主に将来の私へ向けて) 書き記しておく。 + +
+
+ Hugo から Asciidoctor へ + + 最初に断っておくと、Hugo は大変に優れた静的サイトジェネレータである。移行の理由の大半は、自分でジェネレータを書きたかったからに他ならない。 + 実のところ、この記事を執筆している現在、自作ジェネレータは Hugo よりも機能が劣っている。 + 例えば、Hugo を使っていたころはサポートしていた RSS フィードの生成は、まだ実装できていない。 + + + 移行先のフォーマットとして AsciiDoc を選んだのは、Markdown よりも表現力に優れるからである。Markdown は広く使われている軽量マークアップ言語だが、以下のような欠点を持つ。 + + + + CommonMark では機能が貧弱である (例: 脚注、id 属性の付与) + 拡張記法に実装間で互換性がない + メタデータ (公開日など) を埋め込む統一された方法がない + + + + AsciiDoc は Markdown に比べると普及していないが、上記の欠点は克服している。 + + + + ブログを書くのに十分な表現力がある + フォーマットを拡張するときの記法があらかじめ定められている + メタデータを埋め込む統一された方法がある + + + + なお、Hugo は AsciiDoc もサポートしているのだが、AsciiDoc を使う場合 Asciidoctor を別途インストールする必要があり、それならば最初から Asciidoctor でよかろうと移行を決めた。 + +
+
+ Asciidoctor から自前のジェネレータへ + + AsciiDoc は良いフォーマットだが、私には 1点不満があった。それは、高い表現力を担保するために記号が使い倒されており、エスケープが難しいという点だ (具体例を挙げたいのだが、何だったか覚えていない)。これは、多種多様な記号類を入力する必要のある技術ブログにとっては辛い問題である。この問題を解決するため、 + + 表現力が高く、 + 文法が厳密であり、 + 簡単に実装できる + + フォーマットが求められた。これに合致したのが、XML をベースとする DocBook (今回使っているのは、そのサブセットである Simplified DocBook) である。 + + + 実は、AsciiDoc と DocBook はおおよそ互換性がある。AsciiDoc で書かれた文書は (ほぼ) 情報ロスなしに DocBook へ変換でき、逆もまたしかりである。 + よって、DocBook には、AsciiDoc と同等の表現力がある。 + + + XML の文法の厳密さについては、説明するまでもないだろう。また、単純な文法であることから実装が容易であり、事実上 Asciidoctor へロックインされる AsciiDoc とは異なり、さまざまな言語で多くのライブラリが存在する。 + + + 今回は、XML のパース自体も自分で書いている (これは何となく書きたかったからであり、合理的な理由があるわけではない。実装はサボりまくっているので XML のコメントが使えないといった制限がある)。 + + + XML という機械処理しやすいフォーマットを選ぶことには、機械的な変換や検査といった処理がおこないやすくなるといった利点もある。 + 欠点は軽量マークアップ言語と比べて冗長であることだが、書く際は補完などを用いるのでそれほど気にならない。 + 結局のところ、技術ブログの執筆を律速するのは調査と文章の記述であり、マークアップの手段は執筆時間に大した影響を与えない。 + +
+
+ おわりに + + 2度のリライトを経て、記事のフォーマットとサイトジェネレータを上から下まで掌握した。 + 今後も改善のアイデアは多数あるので、じわじわと進めていきたいところだ。 + + + 最後にもう一度書くのだが、Hugo は大変に優れた静的サイトジェネレータである。 + 無駄な拘りがなければこれを使うとよい。 + 私は無駄に拘ったので、ブログの記事を書く時間を潰してブログシステムを作ってしまった。 + +
+
diff --git a/vhosts/blog/content/posts/2023-04-01/implementation-of-minimal-png-image-encoder.xml b/vhosts/blog/content/posts/2023-04-01/implementation-of-minimal-png-image-encoder.xml new file mode 100644 index 00000000..67dfad87 --- /dev/null +++ b/vhosts/blog/content/posts/2023-04-01/implementation-of-minimal-png-image-encoder.xml @@ -0,0 +1,607 @@ + +
+ + PNG 画像の最小構成エンコーダを実装する + + PNG 画像として valid な範囲で最大限手抜きしたエンコーダを書く。 + + + + 2023-04-01 + 公開 + + + +
+ はじめに + + この記事では、PNG 画像として valid な範囲で最大限手抜きしたエンコーダを書く。 + PNG 画像に対応したビューアであれば読み込めるが、圧縮効率については一切考えない。 + また、実装には Go 言語を使うが、Go の標準ライブラリにあるさまざまなアルゴリズム (PNG 画像に関係する範囲だと、zlib や CRC32、Adler-32 など) は使わない。 + +
+
+ PNG ファイルの基本構造 + + PNG ファイルの基本構造は次のようになっている。 + + + PNG signature + IHDR chunk + 任意個の chunk + IEND chunk + + + Chunk には画像データを入れる IDAT chunk、パレットデータを入れる PLTE chunk、テキストデータを入れる tEXt chunk などがあるが、 + 今回は最小構成ということで IDAT chunk (と IHDR chunk と IEND chunk) のみを用いる。 + + + 次節で、それぞれの具体的な構造を確認しつつ実装していく。 + +
+
+ PNG のエンコーダを実装する + + 以下のソースコードをベースにする。 + 今回 PNG のデコーダは扱わないので、読み込みには Go の標準ライブラリ image/png を用いる。 + + + + + + 以降は、writeSignaturewriteChunkIhdr などを実装していく。 + +
+ PNG signature + + PNG signature は、PNG 画像の先頭に固定で付与されるバイト列で、8 バイトからなる。 + + + 0x89 + 0x50 (ASCII コードで「P」) + 0x4E (ASCII コードで「N」) + 0x47 (ASCII コードで「G」) + 0x0D (ASCII コードで CR) + 0x0A (ASCII コードで LF) + 0x1A (ASCII コードで EOF) + 0x0A (ASCII コードで LF) + + + CRLF や LF は、送信中に改行コードの変換が誤っておこなわれていないかどうかを検知するのに使われる。 + + + writeSignature の実装はこちら: + + + + + + encoding/binary パッケージの binary.Write を使い、固定の 8 バイトを書き込む。 + +
+
+ Chunk の構造 + + IHDR chunk に進む前に、chunk 一般の構造を確認する。 + + + Length: chunk data のバイト長 (符号なし 4 バイト整数) + Chunk type: chunk の種類を示す 4 バイトからなる名前 + Chunk data: 実際のデータ。0 バイトでもよい + CRC: chunk type と chunk data の CRC (符号なし 4 バイト整数) + + + CRC (Cyclic Redundancy Check) は誤り検出符号の一種。Go 言語では hash/crc32 パッケージにあるが、今回はこれも自前で実装する。PNG の仕様書に C 言語のサンプルコードが載っている (D. Sample CRC implementation) ので、これを Go に移植する。 + + + > 1) + } else { + c = c >> 1 + } + } + crcTable[n] = c + } + crcTableComputed = true + } + + func updateCrc(crc uint32, buf []byte) uint32 { + if !crcTableComputed { + makeCrcTable() + } + + c := crc + for n := 0; n < len(buf); n++ { + c = crcTable[(c^uint32(buf[n]))&0xFF] ^ (c >> 8) + } + return c + } + + func crc(buf []byte) uint32 { + return updateCrc(0xFFFFFFFF, buf) ^ 0xFFFFFFFF + } + ]]> + + + できた crc 関数を使って、chunk 一般を書き込む関数も用意しておこう。 + + + + + + 仕様どおり、chunkTypedata から CRC を計算し、data の長さと合わせて書き込んでいる。 + PNG では基本的に big endian を使うことに注意する。 + + + 準備ができたところで、具体的な chunk をエンコードしていく。 + +
+
+ IHDR chunk + + IHDR chunk は最初に配置される chunk である。次のようなデータからなる。 + + + 画像の幅 (符号なし 4 バイト整数) + 画像の高さ (符号なし 4 バイト整数) + + ビット深度 (符号なし 1 バイト整数) + + 1 色に使うビット数。1 ピクセルに 24 bit 使う truecolor 画像では 8 になる + + + + 色タイプ (符号なし 1 バイト整数) + + 0: グレースケール + 2: Truecolor (今回はこれに決め打ち) + 3: パレットのインデックス + 4: グレースケール + アルファ + 6: Truecolor + アルファ + + + + 圧縮方式 (符号なし 1 バイト整数) + + PNG の仕様書に 0 しか定義されていないので 0 で固定 + + + + フィルタ方式 (符号なし 1 バイト整数) + + PNG の仕様書に 0 しか定義されていないので 0 で固定 + + + + インターレース方式 (符号なし 1 バイト整数) + + 今回はインターレースしないので 0 + + + + + 今回ほとんどのデータは決め打ちするので、データに応じて変わるのは width と height だけになる。コードは次のようになる。 + + + + +
+
+ IDAT chunk + + IDAT chunk は、実際の画像データが格納された chunk である。IDAT chunk は deflate アルゴリズムにより圧縮され、zlib 形式で格納される。 + +
+ Zlib + + まずは zlib について確認する。おおよそ次のような構造になっている。 + + + 固定で 0x78 (符号なし 1 バイト整数) + 固定で 0x01 (符号なし 1 バイト整数) + データ + データの Adler-32 + + + 最初の 2 バイトにも意味はあるが、PNG では固定で構わない。 + + + Adler-32 も CRC と同じく誤り検出符号である。こちらも zlib の仕様書に C 言語でサンプルコードが記載されている (9. Appendix: Sample code) ので、Go に移植する。 + + + > 16) & 0xFFFF + + for n := 0; n < len(buf); n++ { + s1 = (s1 + uint32(buf[n])) % adler32Base + s2 = (s2 + s1) % adler32Base + } + return (s2 << 16) + s1 + } + + func adler32(buf []byte) uint32 { + return updateAdler32(1, buf) + } + ]]> + + + 「データ」の部分には圧縮したデータが入るのだが、真面目に deflate アルゴリズムを実装する必要はない。Zlib には無圧縮のデータブロックを格納することができるので、これを使う。本来は、データの圧縮効率の悪いランダムなデータをそのまま格納するためのものだが、今回は deflate の実装をサボるために使う。 + + + 1 つの無圧縮ブロックには 65535 (216 - 1) バイトまで格納できる。それぞれのブロックは次のような構成になっている。 + + + 最終ブロックなら 1、そうでなければ 0 (符号なし 1 バイト整数) + ブロックのバイト長 (符号なし 2 バイト整数) + ブロックのバイト長の 1 の補数、あるいはビット反転 (符号なし 2 バイト整数) + データ (最大 65535 バイト) + + + 実際にこの手抜き zlib を実装したものがこちら: + + + + +
+
+ 画像データ + + では次に、zlib 形式で格納するデータを用意する。PNG 画像は次のような順にスキャンする。 + 画像の左上のピクセルから同じ行を横にスキャンしていき、一番右まで到達したら次の行の左に向かう。 + 右下のピクセルまで行けば終わり。要は Z 字型に進んでいく。 + + + また、それぞれの行の先頭には、圧縮のためのフィルタタイプを指定する。 + ただ、今回はその実装を省略するために、常にフィルタ 0 (何も加工しない) を使う。 + + + 先ほどの encodeZlib も使って実際に実装したものがこちら: + + + + +
+
+
+ IEND chunk + + 最後に IEND chunk を書き込む。これは PNG 画像の最後に配置される chunk で、PNG のデコーダはこの chunk に出会うとそこでデコードを停止する。 + + + 特に追加のデータはなく、必要なのは chunk type の IEND くらいなので実装は簡単: + + + + +
+
+
+ おわりに + + 最後に全ソースコードを再掲しておく。 + + + > 1) + } else { + c = c >> 1 + } + } + crcTable[n] = c + } + crcTableComputed = true + } + + func updateCrc(crc uint32, buf []byte) uint32 { + if !crcTableComputed { + makeCrcTable() + } + + c := crc + for n := 0; n < len(buf); n++ { + c = crcTable[(c^uint32(buf[n]))&0xFF] ^ (c >> 8) + } + return c + } + + func crc(buf []byte) uint32 { + return updateCrc(0xFFFFFFFF, buf) ^ 0xFFFFFFFF + } + + const adler32Base = 65521 + + func updateAdler32(adler uint32, buf []byte) uint32 { + s1 := adler & 0xFFFF + s2 := (adler >> 16) & 0xFFFF + + for n := 0; n < len(buf); n++ { + s1 = (s1 + uint32(buf[n])) % adler32Base + s2 = (s2 + s1) % adler32Base + } + return (s2 << 16) + s1 + } + + func adler32(buf []byte) uint32 { + return updateAdler32(1, buf) + } + ]]> + +
+
+ 参考 + + Portable Network Graphics (PNG) Specification (Third Edition) + ZLIB Compressed Data Format Specification version 3.3 + +
+
diff --git a/vhosts/blog/content/posts/2023-04-04/phperkaigi-2023-report.xml b/vhosts/blog/content/posts/2023-04-04/phperkaigi-2023-report.xml new file mode 100644 index 00000000..18bc96ce --- /dev/null +++ b/vhosts/blog/content/posts/2023-04-04/phperkaigi-2023-report.xml @@ -0,0 +1,187 @@ + +
+ + PHPerKaigi 2023 参加レポ + + 2023-03-23 から 2023-03-25 にかけて開催された、PHPerKaigi 2023 に参加した。 + + + conference + php + phperkaigi + + + + 2023-04-04 + 公開 + + + 2023-06-28 + トークセッションの記事版の執筆を中止 + + + +
+ はじめに + + 2023-03-23 から 2023-03-25 にかけて開催された、PHPerKaigi 2023 に参加した。 + 今年は 2つのセッションのスピーカーとして、また、当日スタッフとして参加した。 + + + 昨年、一昨年の参加レポはこちら: + + + PHPerKaigi 2022 + PHPerKaigi 2021 + +
+
+ スピーカーとして + + これまでとの最大の違いとして、今回はスピーカーとして登壇した。まずはそれについて書く。2つのセッションで登壇した。 + + + + 詳説「参照」:PHP 処理系の実装から参照を理解する + + プロポーザル + スライド + 解説記事 (執筆中) → 追記: 記事版の執筆は諦めた + + + + PHPerチャレンジ解説セッション - デジタルサーカス株式会社 + + プロポーザル + スライド + 解説記事 (執筆中) → 追記: 記事版の執筆は諦めた + + + + + PHPer チャレンジの話については後述する。 + 参照については、PHP を書き始めた頃からずっと疑問に思っていたので、仕組みを理解する良い機会となった。 + +
+
+ 当日スタッフとして + + 今回はスピーカーのみならず当日スタッフとしても参加した。 + カンファレンスのスタッフとしての参加は初めてだったが、初参加のスタッフでもスムーズに作業ができるような仕組みが整えられていた。 + + + PHPerKaigi は一般参加者の目線でもよくできたカンファレンスだなあという印象だったのだが、よりその思いを強くした。 + なんとスタッフにとってもよくできたカンファレンスなのである。 + + + 反省点は私自身の最大 HP がまったく足りていなかったことで、次の機会には最後まで動けるようにしたいところである。 + +
+
+ 参加者として +
+ おすすめセッション + + 5つのセッションを厳選した。 + + + ブラウザの向こう側で「200 OK」を返すまでに何が起きているのか調べてみた + + + Web に関わるなら、バックエンドでもフロントエンドでも知っておいてほしい知識。 + タイトルを見て「こんな話だろうな」と想像がつくレベルなら見なくてもいいかも。 + + + PHPで学ぶ "Cacheの距離" の話 + + + これも上セッションと同様に、基礎を抑えられる良いセッション。 + + + 防衛的 PHP: 多様性を生き抜くための PHP 入門 + + + 静的解析ツールの話。静的解析は PHP のみならず最近の動的言語の一大潮流なので、逃れられない。 + + + PHPの最高機能、配列を捨てよう!! + + + 実はこれも上のセッションと同様の話。 + PHP の静的解析ツールは配列にも (無理矢理) 型が付けられるものが多いが、実行時にも検査できるという点において専用のクラスを作る方が優れている。 + + + 時間を気にせず普通にカンニングもしつつ ISUCON12 本選問題を PHP でやってみる + + + 個人的に最も楽しみにしていたセッションであり、今回のモリアガリトーク賞 (盛り上がったセッションに運営側から贈られる賞) でもある。 + ネタバレになるが、最終的に (Go で実装された) 本戦優勝スコアを超えている。 + +
+
+ PHPer チャレンジ + + 昨年に引き続き、弊社デジタルサーカス株式会社からのトークン問題の作題を担当した。 + また、今年はさらに作成した問題を解説するセッションにも登壇した。 + 今年のトークンは、昨年の PHPerKaigi 2022 が終わった段階から作り始め、約半年かけて制作した。 + + + 問題の制作中は大変楽しかったが、まあやりすぎた。 + いかに超絶技巧を凝らすかに注力してしまい、解く楽しさという観点を失ってしまったきらいがある。 + + + (WIP: 解説ブログ記事執筆中。終わったらここにリンク) + +
+
+ 雑多な感想 + + なんかいろいろ。 + + + マカロンおいしかった + \ペチパー/ + 名札便利 + \ペチパー/ + 傘袋便利 + \ペチパー/ + パーカーのデザイン良き + + + (あとから見返して自分でもわけがわからなくなりそうなので書いておくと、会場に入場する際に名札をタッチすると小桜エツコさんの声で「ペチパー」という音声が流れるギミックがあった) + +
+
+
+ おわりに + + 去年の参加レポでは、来年の目標として次を挙げた。 + +
+ + プロポーザルを出す + PHPer チャレンジのトークン問題を 5題作成する + 現地に行く + PHPer チャレンジで圧勝する + +
+ + プロポーザルに関しては採択されて登壇できたし、PHPer チャレンジは解説もおこなった。また、現地に行くだけでなく、当日スタッフとして参加した。 + 4つ目の PHPer チャレンジに関しては、今年は参加していない。 + スタッフをやりながらだと入力する時間も探す時間も取れそうになかったのと、スタッフをやっている関係で少しだけ早く入手してしまうトークンがいくつか存在していたため。 + + + カンファレンス全体の感想についてだが、大規模なカンファレンスにオフラインで参加するのは今回が初めてだったので、その話をしたい。 + + + オンラインとオフラインだと体験が別物になる。そもそもが似て非なるものなのだ。 + 向き不向きはあるだろうが、オンラインしか参加したことのないという方は、一度現地参加してみてはいかがだろうか。 + + + さて、参加レポは去年も一昨年もこの言葉で締め括っているので、今年もそれで終わろうと思う。 + + + ではまた来年。 + +
+
diff --git a/vhosts/blog/content/posts/2023-06-25/phpconfuk-2023-report.xml b/vhosts/blog/content/posts/2023-06-25/phpconfuk-2023-report.xml new file mode 100644 index 00000000..d713773e --- /dev/null +++ b/vhosts/blog/content/posts/2023-06-25/phpconfuk-2023-report.xml @@ -0,0 +1,109 @@ + +
+ + PHP カンファレンス福岡 2023 参加レポ + + 2023-06-24 に開催された、PHP カンファレンス福岡に参加した。 + + + conference + php + phpconfuk + + + + 2023-06-25 + 公開 + + + +
+ はじめに + + 2023-06-24 に開催された、PHP カンファレンス福岡 2023に参加した。 + また、その前日に催された、非公式の前夜祭にも参加した。 + 前夜祭では、15分の登壇もおこなった。登壇の方の資料はこちら。 + +
+
+ セッションの感想 +
+ 前夜祭 + + ※セッションの題名と発表者名は、前夜祭イベントの connpass ページから引用。 + + + + スクラム(の一部)を導入してよくなったこと (asumikam さん) + + + スクラムの「一部」を導入されたということでしたが、理想的な形で改善が進んでいるように見受けられました。 + 特に、ブランチ運用やデプロイ頻度、フィードバックサイクルに大きく変化が起きているのは驚くべき成果だと感じました。 + + + + + 地方の小さな勉強会を一番の活動舞台にする (tomio さん) + + + すさまじいほどの「熱」を感じました。 + 私自身、最近になってカンファレンスや勉強会への参加・登壇を活発におこなうようになったことで、頷く点が多かったです。 + + + + +
+
+ カンファレンス + + ※セッションの題名と発表者名は、カンファレンスの fortee ページから引用。 + + + + 育成力 - エンジニアの才能を引き出す環境とチューターの立ち回り - (岡嵜 雄平 さん) + + + ちょうど弊チームに新規メンバがジョインしたばかりで、オンボーディングプロセスについて考えていたところの発表でした。 + すぐにすべてを取り入れるというわけにはいきませんが、弊社での新人育成プロセスの改善につながるヒントをいくつか得られたと思います。 + + + + + オブジェクト指向は本当に必要か? (たなかひさてる さん、こいほげ さん) + + + ※当日 D ホールでおこなわれたアンカンファレンスセッションのため、正式タイトル・リンクなし + + + 私自身、「オブジェクト指向」については色々と言いたいことがあるのですが、だいたいツイートしたこれとこれです。 + + + 「オブジェクト指向の話は、パラダイムの異なる複数の言語に触れているかどうかで見え方がまったく異なる印象がある。OOPはどうでもいいです (※個人の感想です)」 (Twitter のツイートへのリンク) + + + 「OOPは現代の言語で考える意味はほぼない古いパラダイムだよという立場ですが、OOPについてあまり大っぴらに話してると色んなところから刺されそうなんですよね (Twitterは大っぴらじゃないんですか?)」 (Twitter のツイートへのリンク) + + + + + + + その説明、コードコメントに書く?コミットメッセージに書く?プルリクエストに書く? (おかしょい/岡田正平 さん) + + + Twitter にもツイートしましたが、完全に自分の意見と一致していたので、とても共感できました。 + 今後は社内のコードレビュー時に、こちらの資料を貼りつけることにします。 + + + + +
+
+
+ おわりに + + 居住地域から離れた場所への遠征参加は初めてだったが、大変楽しい (しかも勉強にもなる!) 体験だった。 + 受け取った「熱」が冷める前に、自らの手を動かしていきたい。 + +
+
-- cgit v1.2.3-70-g09d2