無駄と文化

実用的ブログ

PHPカンファレンス福岡2024にスタッフとして参加してきました

6月22日に開催された PHP カンファレンス福岡2024にスタッフとして参加していました。(1年ぶり2回目)

3行まとめ

  • 私の地元福岡で無事にカンファレンスが開催され、参加者が喜んでくれて嬉しいです

PHP カンファレンス福岡2024とは

phpcon.fukuoka.jp

基本は PHP 言語を用いたソフトウェアエンジニアリングのカンファレンスですが、その周辺話題であれば PHP に直接関係ないことまで多く取り上げられています。

300名近い人が参加してくれていたらしいです。

 

なぜスタッフとして参加したか

約10年前、20代後半でソフトウェアエンジニアになった私にとって PHP カンファレンス福岡は福岡におけるエンジニアのコミュニケーションが可視化される場でした。そこにはエンジニアの活力・モチベーション・コミュニケーションが渦巻いていて、福岡の地でエンジニアをしていくことについて大きく背中を押してもらった記憶があります。

コロナ禍で数年間開催されず、ようやく再開した PHP カンファレンス福岡にスタッフとして参加することは私にとってコミュニティへの恩返しの気持ちがあります。

 

運営を通じて考えたこと・感じたこと

今年は (今年も?) 運営を通じてカンファレンスのあり方について考えさせられる場面が多くありました。

言うまでもないことと発信すべきことの境目

スタッフ側としてみんなが迷わずに楽しんでもらえるよう情報発信しなければという思いで準備していました。 が、あらためて考えると発信するべきことって何?という問いもなかなかに難しいものです。

私個人としてこれまでにいくつかのカンファレンスに参加・登壇などしてきて、良くも悪くもカンファレンス慣れしていました。 慣れた頭で考える「よくあるコンテンツ」「よくあるトーク内容」「よくある動線」「よくあるタイムスケジュール」...それら一つひとつが初参加でもちゃんと分かるものだろうか?とあらためて考えさせられました。

もしも「いつものことだから発信しなくても分かるだろう」と発信を怠れば、結果として初参加者を遠ざけて慣れた人だけを受け入れることになってしまいます。そうするとコミュニティへの新しい人の流入が止まり、古びたつまらないコミュニティになってしまうかもしれません。

そんなことを思いながら、参加してくれる全ての人にどのような声掛けをすればフレッシュな気持ちで楽しんでもらえるだろうかと考えました。

 

参加者の数だけカンファレンスに対する期待がある

今回、アンカンファレンスルームやスポンサーブースルームの企画に関わって、参加者の数だけカンファレンスに対する期待があることに気づきました。

  • 一般参加者として参加した人の期待
    • 初参加の人の期待
    • カンファレンス慣れした人の期待
  • 登壇者として参加した人の期待
  • スポンサーとして参加した人の期待
  • そして、スタッフとして参加した人の期待

カンファレンスがどうあるべきか考えたとき、一般参加者に面白い話題を提供するという目線だけでは語りきれません。 お金を出してくれているスポンサーさまは何を期待しているか、時間を使ってトークの準備をしている登壇者は、同じく時間を使っているスタッフは。 みんなにとっていい時間にしたい、でもどうやって?準備するにあたって迷うこともありました。

さらに言うと、一般参加者同士のコミュニケーション、登壇者と一般参加者のコミュニケーション、スポンサーさまと参加者のコミュニケーション、など人と人とのシナジーのあり方にも期待感があるはずです。

多くの人の支えで成り立っているカンファレンスだからこそ多くの期待があるはずで、その期待にいくらかでも応えられただろうかと振り返り、自問自答したいりしています。

 

一参加者としての感想

さて、スタッフとしての役割・仕事があったとはいえ私も一参加者として1日楽しんだので感想を。 ...カンファレンスを普通に素直に楽しめず、メタ的な楽しみに走ってしまうことに課題感を感じる!!!!!

はい、20代のころはカンファレンスで得る知識にいちいち感心しワクワクしていたのに、最近はカンファレンスのトークを聞いても「へー、そういうのもあるんだ」くらいの乾いた感想しか持てなくなっている自分がいます。 技術に関心が無くなったのか?飽きたのか?向上心が尽きたのか?

それを打開するために (?) スタッフ参加というメタ的な楽しみ方をしているのがあまり褒められたものじゃないなーという気持ちです。

 

これから1年間、参加者としてのカンファレンスの楽しみを再認識するために頭と時間とお金を使っていこうかな、と気持ちを新たにしています。

 

まとめ

繰り返しになりますが、私の地元福岡で無事にカンファレンスが開催され、参加者が喜んでくれて嬉しいです。

 

 

私からは以上です。

WordPress でピクロスを作問・投稿できるカスタムブロックを作った

ピクロスというパズルをご存知でしょうか。お絵描きロジック, ののぐらむ などとも呼ばれる、縦横に並んだ数字をヒントにドット絵を完成させるパズルゲームです。
WordPress でピクロスを作問・投稿するためのカスタムブロック wp-block-nonogram を作成したので紹介します。

 

目次

 

wp-block-nonogram はこんなやつ

プラグインを有効化すると WordPress の編集画面で "Nonogram Puzzle" というブロックが使えるようになります。
そして、

作問の様子

ブロックエディタでドット絵が打てるようになります。

作問が完了して投稿を公開すると、

解答している様子

記事のページ上でピクロスを遊ぶことができます。

 

使い方

GitHub でプラグインの ZIP ファイルを公開しています。
ダウンロードして展開し wp-content/plugins ディレクトリに配置した後に管理画面からプラグインを有効化すると "Nonogram Puzzle" ブロックが使えるようになります。

github.com

WordPress 公式のブロックディレクトリに登録するために現在申請中です。
申請が通ったら、管理画面上からもっと手軽にブロックを使い始められるようになるはずです。

 

本文の編集領域にカーソルを置いた状態で /nonogram と入力するか、ブロック追加から "nonogram" で検索すると "Nonogram Puzzle" ブロックが挿入されて作問できるようになるはずです。

プラスマークのボタンからブロックを選択して挿入してもいい

行数・列数を自由に増減させて作問できます。ブロックは1記事に複数配置することも可能です。

行数・列数は自由に変更できる

作問が終わって記事を保存して公開すれば、サイトを訪れたユーザーが問題を解くことができます。

 

なぜこんなものを作ったの

非実用的なネタブロックを作るのに憧れがあったからです。
WordPress におけるカスタムブロックは、ほぼただの React コンポーネントです。そのため JavaScript で実装されたゲームならそのままブロックエディタ上で動作させられます。

ja.wordpress.org

私が好きな非実用ブロックはこの ピアノブロック です。ブログ用の記事編集からかけ離れすぎなコンセプト、やたら高機能なところが最高です。

私もなにかネタブロックを作りたいと思い、ちゃちゃっと実装でそうな題材としてピクロスを選びました。
が、ちゃんと作問と投稿ができる実用的なブロックに仕上がってしまったことを反省しています。

 

実装上のおもしろポイント

はじめて <canvas> を本格的に使ってブラウザ上で動くゲームを作ったので楽しかったです。(感想)

最初は『縦横に並んだセルを塗ったり消したりするなら <input type="checkbox"> を並べたらええな!』と思っていたのですが。縦30セル × 横30セル の問題を作るとチェックボックスを900個配置することになるので無理過ぎました。
いくら仮想 DOM をもつ React といえども取り扱いうタグが増えるほど差分管理が重くなるので、パフォーマンス観点から <canvas> を使うことにしました。

<canvas> にあれこれ描画するには konva と (それを React 向けにラップした) react-konva というライブラリを使っています。
特に react-konva は、まるで HTMLElement を扱う感覚で <canvas> に描画した図形を扱えて便利でした。

 

今後の展望

もっともっと非実用的なブロックを作りたい!という思いがあるので、次回作としてマインスイーパーブロックを作ろうかなと構造しています。
編集画面でマインスイーパーが遊べて、記事の方ではゲームの結果が表示される (ので自慢できる) というようなブロックになる予定です。

 

 

私からは以上です。

指定したキーだけを省略可能にするユーティリティタイプを書く

TypeScript は便利だ。型検査で値が保証されるのはとても頼もしい。
とはいえ場合によっては型検査を通すために不必要にタイプ量が増えてしまうことがある。

例えば下記のような型が 外部ライブラリによって生成される としよう。

type User = {
    __typename: 'user';
    id: string;
    name: string;
    gender: 'male' | 'female';
    department: {
        __typename: 'department';
        id: string;
        name: string;
    };
};

いま User 型の値を生成して使いたい。ただし、とても重要な前提条件として キー __typename には関心がない とする。

const user: User = {
    __typename: 'user',  // このキーは使うつもりがないし関心もない
    id: 'u001',
    name: 'SUZUKI Ichiro',
    gender: 'male',
    department: {
        __typename: 'department',  // このキーは使うつもりがないし関心もない
        id: 'd001',
        name: 'sales',
    }
};

このような値を定義すれば型検査をパスする。 が、使うつもりのない __typename をわざわざ書くのは面倒だと思った、そう仮定しよう。

 

特定のキーを省略可能にしたい

type User においてキー __typename は必須だった。もしも何らかの方法で以下のような型 BetterUser を生成できたら。

type BetterUser = {
    __typename?: 'user';
    id: string;
    name: string;
    gender: 'male' | 'female';
    department: {
        __typename?: 'department';
        id: string;
        name: string;
    };
};

__typename?: となっているのに注目してもらいたい。キー __typename は省略可能なので以下のような値でも受け入れられる。

const user: BetterUser = {
    // __typename を書かなくて済む
    id: 'u001',
    name: 'SUZUKI Ichiro',
    gender: 'male',
    department: {
        // __typename を書かなくて済む
        id: 'd001',
        name: 'sales',
    }
};

いかにも良さそうだ。__typename を省略可能にするにはどうすればいいだろうか。

 

ユーティリティタイプでどうにかする

既存の型をもとに別の型を生成する 型レベル関数 のようなものを TypeScript においては「ユーティリティタイプ」と呼んでいる。1

例えば、全てのキーを省略可能するユーティリティタイプ Partial<T> は次のように書ける。

type Partial<T> = { [K in keyof T]?: T[K] };

動作イメージはこうだ、

type User = {
    __typename: 'user';
    id: string;
    name: string;
    gender: 'male' | 'female';
};

type PartialUser = Partial<User>;
// => type PartialUser = {
//     __typename?: 'user' | undefined;
//     id?: string | undefined;
//     name?: string | undefined;
//     gender?: 'male' | 'female' | undefined;
// };

実はこのようなユーティリティタイプ Partial<T> は TypeScript に最初から用意されている。そのためわざわざ定義しなくても使える。とても便利だ。

だが今回は 指定したキーだけ 省略可能にしたいのだった。全てのキーを省略可能する Partial<T> は少しやりすぎだ。

 

Partial<T> をそのまま使うことは出来なそうだが「指定したキーだけ省略可能にするユーティリティタイプを定義して使う」という方針は決まった。
先に名前を決めてしまおう SelectivePartial というユーティリティタイプが欲しい。

type SelectivePartial<T, K> = {};  // TODO: 実装する

type BetterUser = SelectivePartial<User, '__typename'>;

このように書けると良さそうだ。

 

Partial を応用した単純な例

指定したキーだけ省略可能にするのはわりと簡単で、ChatGPT に頼むとサラッと書いてくれる。

type SelectivePartial<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;

このように。

Omit<T, K>Pick<T, K> も TypeScript に標準のユーティリティタイプだ。Omit<T, K>K で指定したキーを T から除外してくれる。Pick<T, K> はその逆で、K で指定したキーのみ T から残してくれる。

type User = {
    __typename: 'user';
    id: string;
    name: string;
    gender: 'male' | 'female';
};

type Omitted = Omit<User, '__typename'>;  // User から __typename を除外
// => type Omitted = {
//     id: string;
//     name: string;
//     gender: 'male' | 'female';
// };

type Picked = Pick<User, '__typename'>;  // User から __typename だけを残す
// => type Picked = {
//     __typename: 'user';
// };

これと先ほどの Partial<T> を組み合わせて、さらに & でつないで交差型 (Intersection Types) を作ると「指定したキーだけ省略可能にする」が実現できる。

type Parted = Partial<Pick<User, 'name'>>;  // name だけを残し、省略可能にする
// => type Parted = {
//     __typename?: 'user' | undefined;
// };

type BetterUser = Omitted & Parted;
// => type BetterUser = {
//     __typename?: 'user' | undefined;
//     id: string;
//     name: string;
//     gender: 'male' | 'female';
// };

一連の処理をまとめて型変数を使って書くと最初に示した SelectivePartial<T, K> の定義になる。

type SelectivePartial<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;

type BetterUser = SelectivePartial<User, '__typename'>;
// => type BetterUser = {
//     __typename?: 'user' | undefined;
//     id: string;
//     name: string;
//     gender: 'male' | 'female';
// };

最低限の要件を満たしてはいるが不満が残る。この方法ではネストしたオブジェクト型に再帰的に作用させることはできない。

 

SelectivePartial を再帰的に作用させたい

最初の例をもう一度見てみよう。最初に示した User 型はこんな感じだった。

type User = {
    __typename: 'user';
    id: string;
    name: string;
    gender: 'male' | 'female';
    department: {
        __typename: 'department';
        id: string;
        name: string;
    };
};

SelectivePartial<T, K>User に作用させると次のようになる。

type SelectivePartial<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;

type BetterUser = SelectivePartial<User, '__typename'>;
// => type BetterUser = {
//     __typename?: 'user' | undefined;
//     id: string;
//     name: string;
//     gender: 'male' | 'female';
//     department: {
//          __typename: 'department';
//          id: string;
//          name: string;
//     };
// };

department の中の __typename はあいかわらず省略できない。

 

ユーティリティタイプを再起的に作用させるには、関数の再帰呼び出しのアイデアを借用すればいい。と、その前に Omit<T, K>Partial<Pick<T, K>> を自前の実装で書き直す必要がある。
Omit<T, K>Pick<T, K> とても似たコードで実装できる。

type Omit<T, K> = {
    [L in keyof T as L extends K ? never : L]: T[L];
};

type Pick<T, K> = {
    [L in keyof T as L extends K ? L : never]: T[L];
};

どちらの実装も L in keyof T の部分で T の各キーに対して作用を起こしている。
Omit の方では L extends K ? never : L で「キー LK に含まれていればキー L を消し去る」という作用を表現している。Pick はその逆で、L extends K ? L : never で「キー LK に含まれていなければキー L を消し去る」という作用を表現している。

Pick<T, K> の自前実装をもとに Partial<Pick<T, K>> も自前で書き下したい。じつはこれはとても簡単で、キーの後ろに ? を打って省略可能であることを表明するだけだ。

type PartialPick<T, K> = {
    [L in keyof T as L extends K ? L : never]?: T[L];
};

ここまでのコードをまとめて前の節で書いた再帰的でない SelectivePartial<T, K> を書き直してみよう。

// type SelectivePartial<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;

type SelectivePartial<T, K> = {
    [L in keyof T as L extends K ? never : L]: T[L];
} & {
    [L in keyof T as L extends K ? L : never]?: T[L];
};

ここまでくればユーティリティタイプを "再帰呼び出し" するのは簡単だ。T[L] の部分をちょっといじってあげればよい。

type SelectivePartial<T, K> = {
    [L in keyof T as L extends K ? never : L]: SelectivePartial<T[L], K>;
} & {
    [L in keyof T as L extends K ? L : never]?: SelectivePartial<T[L], K>;
};

改良版の SelectivePartial<T, K> を使ってみよう。

type User = {
    __typename: 'user';
    id: string;
    name: string;
    gender: 'male' | 'female';
    department: {
        __typename: 'department';
        id: string;
        name: string;
    };
};

type BetterUser = SelectivePartial<User, '__typename'>;
// => type BetterUser = {
//     __typename?: 'user' | undefined;
//     id: string;
//     name: string;
//     gender: 'male' | 'female';
//     department: {
//         __typename?: 'department' | undefined;
//         id: string;
//         name: string;
//     };
// };

かなり良くなった。

 

配列型にも適用したい

もしも最初に手元にあるのが User 型ではなく Users 型だったらどうなるだろうか。

type Users = {
    __typename: 'user';
    id: string;
    name: string;
    gender: 'male' | 'female';
    department: {
        __typename: 'department';
        id: string;
        name: string;
    };
}[];

先ほどとよく似ているが [] がついている。「オブジェクト型の配列」型だ。先ほど定義した SelectivePartial<T, K> を使って Users から BetterUsers を作れるだろうか。

 

TypeScript の型についてのちょっとしたテクニックを知っていれば T[]T を相互変換できる。

type A = T[];
type B = A[number];
//   ^? T

type C = T;
type D = C[];
//   ^? T[]

T[] 型の A に対して A[number] とすると T 型が得られる。慣れないと奇妙な感じがするが、イデオムとして覚えてしまうと便利だ。
C = T に対して C[] と書くと T[] が得られるのは、まぁ、自明だろう。

 

このテクニックを使うと SelectivePartial<T, K>Users に適用できるようになる。

type Users = {
    __typename: 'user';
    id: string;
    name: string;
    gender: 'male' | 'female';
    department: {
        __typename: 'department';
        id: string;
        name: string;
    };
}[];

type User = Users[number];

type BetterUser = SelectivePartial<User, '__typename'>;

type BetterUsers = BetterUser[];
// => type BetterUsers = {
//     __typename?: 'user' | undefined;
//     id: string;
//     name: string;
//     gender: 'male' | 'female';
//     department: {
//         __typename?: 'department' | undefined;
//         id: string;
//         name: string;
//     };
// }[];

もちろんひとまとめにして次のように書いても同じ意味になる。

type BetterUsers = SelectivePartial<Users[number], '__typename'>[];

 

まとめ

Omit<T, K> & Partial<Pick<T, K>> というテクニックは ChatGPT がすぐに教えてくれた。それを再帰的に適用するにはいくつか発想の転換が必要だった。

いまはスッキリ理解できていても TypeScript を書かずにいる期間が空くとすぐに筋力が衰えてしまう。少しでも忘却に抗うためにここに書き記す。

 

 

私からは以上です。

 

おまけ

今回書いたコードがここにある、


  1. TypeScript にはさまざまな 組み込みのユーティリティタイプ がある、知っておくといつか何かの役に立つかもしれない