MADT: Multi Agent Development Team はすぐにやってくる

いつもお世話になっております。わいとんです。

1人で開発チームを回せたらなぁ、って思ったことありませんか?

要件定義、実装、レビュー、QA、それぞれ別の視点が必要じゃないですか。でも人を雇うのは大変だし、コストもかかる。

MADTはその答えになるんじゃないかって思っているんです。

MADTとは

MADT (Multi Agent Development Team) は、複数のAIエージェントが協調して開発業務を遂行するチーム構成のことです。私がここではじめて提唱する概念です。

各エージェントが専門の役割を持って、人間のチームみたいに連携します。私の観測範囲では、これに該当するものもいくつか開発されているようで、すでに実運用されているものもあると思います。

構成例

私が構築したMADTは4人構成です(これについては現状クローズドソース)。ざっと以下の通り。

名前 役割
Mei 要件定義 + PM
Yuki 実装 + インフラ
Priya レビュー + セキュリティ + QA
Amara 分析 + 改善提案

各エージェントはClaude CodeをMCP (Model Context Protocol) 経由で操作して、独立したプロセスとして動きます。

アーキテクチャ

1
2
3
4
5
6
7
8
9
10
┌─────────────────────────────┐
│ Orchestrator (Go) │
│ - Slack/GitHub連携 │
│ - エージェント管理 │
│ - チームチャット │
└──────────────┬──────────────┘
│ MCP (stdio)
┌──────────┼──────────┐
▼ ▼ ▼
[Mei] [Yuki] [Priya] [Amara]

エージェント間のやり取りはチームチャットでやってます。人間も同じチャットに参加できるので、自然にチームの一員として会話できます。

特徴

1. 役割分離による品質担保

実装者とレビュアーが別なので、馴れ合いが起きません。Priyaは遠慮なくYukiのコードに指摘を入れます。

2. 自己改善

すべてのやり取りはログに残ります。Amaraがそれを分析して、改善提案をCLAUDE.mdに反映します。これでチームが自律的に成長していくようになる。

3. 無限ループ対策

レビュー差し戻しが3回を超えたら、上流 (設計や要件) に問題があると判断してエスカレーションします。下流で無理に解決しないようにするためです。

実績

ところで、これをCalciumっていう自作言語の開発に投入したんですが、だいたい3時間で6つのPRがマージされました。

  • コマンド追加
  • オプション追加
  • バグ修正
  • ドキュメント同期

人間1人でやったら数日〜1週間かかる量ですよね。

人間の役割

じゃあなんだい?MADTがあれば人間は不要なのかい?と疑問が浮かんできますよね。でもそうじゃないんです。

人間はスーパーバイザーとして:

  • アーキテクチャ判断
  • エスカレーション対応
  • 最終的な品質担保
  • 顧客とのハイレベルなコミュニケーション

を担います。手を動かすのはAI、判断するのは人間の役目です。

まとめ

MADTは「AIに仕事を奪われる」じゃなくて「AIとチームを組む」っていう発想・概念です。

1人でも開発チームを持てる時代が来てるってことです。しかも爆速で開発してくれる。見えてくるものが変わってくるやつだと思いませんかね。

Calciumというプログラミング言語を作り始めた

いつもお世話になっております。わいとん(@ytnobody)です。

新年も25日が経過し、月日の流れの速さを感じるこの頃です。

さて実は2~3日前くらいからCalciumという言語を作り始めておりまして、今回はその紹介をしておこうということで筆をとった次第です。

Calciumという言語の特徴

ではCalciumの特徴をばばっと列挙しましょう。

  • 100%AIドリブン開発
  • Goで作られている
  • バイトコードへのコンパイル
  • 動的型付け・関数型
  • イミュータブル
  • 制約という仕組み
  • 副作用の可視化
  • 制御構文は基本matchmapだけ
  • 関数の制限
  • イベントドリブンな待ち受け構文

100%AIドリブン開発

Calciumの開発は100%Claude Opus 4.5で行われております。というか、AI以外にコミットさせるケースはほぼないです。今後もほぼ100%AIによってコードが書かれることになります。

これには大変重要な理由があり、もちろんAIもミスをしますが、人間である私はさらにしょっちゅうミスをするから、というものです。人間の中でも私は極めておっちょこちょいですから(注意力3万しかない)、これを信用仕切るのはリスクが大きすぎる。それならまだAIのほうが信用できるというものですし、AIのミスを私が指摘するということもすでに行われていますので(当然逆のほうが多いけど)、昭和的な言い方をするなら、常にダブルチェック体制で開発されているということになります。

あと、AIのほうがコードを素早く作成します。私のように40代半ばの体力が衰えているおっさんなどよりもはるかに素早い。なので、自分の役割はAIに仕様を伝えて設計の伴走をするということに徹しております。

Goで作られている

コンパイラ、トークナイザー、パーサー、レクサー、などなど…いま疲れてるのでアルファベットを打ち込むのが面倒でついついカタカナで書いちゃいましたが、これらのものをすべてGo言語で実装してあります。単純にいろんなOSやCPUアーキテクチャに対応させるうえで、Goは手早く開発できるという経験則があります。

また、AIにとっても比較的書きやすく、学習量も相応にあります。成果物も単体バイナリとなりますから、配布する上で非常に好都合。実行速度も比較的軽快な傾向にありますし、プログラミング言語を作るうえで大変実用的な言語だなあと。私にとってはそれがGoだったのです。

バイトコードへのコンパイル

Calciumは専用VM上で動作する言語として開発をはじめました。そのため、ソースコードをバイトコードへコンパイルする機能があります。

一応REPLもありますし、その気になれば単一実行バイナリを生成することも可能です。

バイトコードへのコンパイル時に、実行速度やコンパイル後の重複コードの最適化も行われます。そのため、一見ムダが大きそうな以下のような処理もほどほど悪くない速度で動作するようになる、と思われます。

1
2
3
4
5
6
7
8
9
use core.io!;

// 2つの値を加算するだけの関数
func add(x, y) = x + y;

// ↓同じ引数で同じ関数を2度コールするがコンパイル時に最適化が行われる
func my_calc(x, y) = add(x, y) * add(x, y);

[2, 3]... |> my_calc !> io.say; // <--- 25

動的型付け・関数型

Calciumは関数型言語でして、いっぽう今さらながら動的型付け言語です。そう、Elixirなんかと同じですね。

でも動的型付けにしたのには私なりに考えがあるんですよ。私、普段はGoやTypeScriptをAIに書かせることがほとんどなんです。しかしですね…PerlやRubyなんかも気まぐれにAIに書かせてみると、これが予想以上にスルスルと意図した通りのコードを生成してくれる。おまけにトークン消費量がTypeScriptやGoなんかよりもだいぶ少ないんですよ。

これは私の体感によるものかなあと思っていたのですが、実際のところ大マジメに検証した人がいるらしく、その結果が以下のポストにチャートとして表わされていました。

ということで、AIフレンドリーにしたいなあ、という欲もありましてこのような選択をしたわけです。

イミュータブル

変数への再代入も厳に禁じました。関数型言語にした理由とも被るところがありますが、やはりAIフレンドリーにしたいという点もありますし、そもそも状態をプログラムの責務として偏在させたくないという思いがあります。

なお、どうしても状態管理(というかイベントドリブンな待ち受け)が必要なシーンではどうするべきか、というのは後で紹介する機能 async.stay でカバーさせることにしました。

制約という仕組み

Calcium言語は動的型付け言語ですから、無対策では関数にどんな値が入ってくるのかわからず、コンパイルする利点がだいぶ薄れてしまいます。

ところが変数への再代入を禁じた上でこの「制約」という仕組みを利用することで、型とは違う方式で安全性を担保しつつ、コンパイル時にも最適化が働くといううまみを得ることを狙いました。

1
2
3
4
5
6
7
8
9
10
use core.io!;

// 0より大きい値であることを制約する
constraint Positive(n) = n > 0;

// 100はPositive制約に準拠している
100 |> Positive? !? {
success(v) => "OK: ${v}" !> io.say
failure(e) => "NG: ${e}" !> io.say
}; // --> OK: 100

なんだよ、ただの名前付きバリデーションじゃないか、と思ったそこのあなた!実に察しがよろしい、その通りなんです!でも、これなら型パズルでAIトークンを消耗することもないですし、ビジネスロジックをそのまま制約として記述できますよね。

ただ、今のところ関数の引数に対してこの制約を適用する機能は未実装です(実装予定はあります)。

副作用の可視化

この言語の結構大きな特徴のひとつがこれ。副作用を可視化することで、ビジネスロジックを純粋に保つことが可能となります。

そのための手段として、関数定義自体がfuncfunc!の2種類ありまして、!が付いている方は副作用があります!という風になっているというわけです。

パイプライン演算子についても|>!>があり、こちらも!が付いている方は右辺に副作用があることを示します。

さらに、モジュール名にも副作用が含まれる場合には!が付く。さっきから例示しているコードにあるuse core.io!;という行ですが、これにも末尾に!が付いている通り、副作用が含まれるモジュールとなります。だいたい標準出力も副作用ですからね。このあたりはとても厳格です。

1
2
3
4
5
6
7
8
use core.io!;

func add(x, y) = x + y;
func my_calc(x, y) = add(x, y) * add(x, y);

[2, 3]...
|> my_calc // <-- これは純粋関数なので普通のパイプライン演算子でコールする
!> io.say; // <-- これはio.sayが副作用をもつ関数なので、副作用パイプライン演算子でコールする

上記の例ではio.sayのところを普通のパイプライン演算子でコールすると以下のように怒られます。

1
2
3
4
5
6
$ ./calcium run ./foo.ca 
./foo.ca: error
line 8, column 5:
|> io.say;
^
cannot use '|>' with effect function; use '!>' instead

制御構文は基本matchmapだけ

この言語の非常にとがった特徴がこのあたりでしょう。if, else, for, while, switch。これらは全部ありません

if, else, switchのかわりにmatchがありますから、そちらを使っていただくということで頑張っていくという言語です。

またwhileasync.stayで代用が可能(なんとコアモジュールに追い出されています!)でして、forのかわりにmapを使ってください、ということです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
use core.io!;

func fizz(n) = match n % 3
0 => "Fizz"
_ => "";

func buzz(n) = match n % 5
0 => "Buzz"
_ => "";

func fizzbuzz_or(result, n) = match result
"" => to_string(n)
_ => result;

func fizzbuzz(n) = fizz(n) + buzz(n)
|> fizzbuzz_or(n);

"FizzBuzz (1-15):" !> io.say;
range(1, 16)
|> map(fizzbuzz)
!> io.say;

上記の例ではFizzBuzz問題を解いていますが、場合分け処理が必要なfizz,buzz,fizzbuzz_orの3つの関数でifなどではなくmatchがつかわれています。

また、数値配列に対して順番に処理を行うためループ処理を行いますが、forではなくmapをつかっています。

関数の制限

なんと、Calciumでは関数はシングルステートメントに限定されます。つまり!2つ以上の処理をしたければパイプラインを使うか、関数を分けるしかないということになります!

なぜそんな制限を設けているのかというと、マルチステートメントをサポートすることで関数自体の複雑度が爆上げしてしまうので、それをなくしたいという思いからこのようになっています。

パイプラインであれば、どこからが副作用でどこまでが純粋なのか一目で把握できます。ビジネスロジックに副作用が入り込んでいることがわかれば、ビジネスロジックのテスタビリティを向上させるために副作用を分離したくなるというものでして、そのようなリファクタを行いやすくするためなのです。

イベントドリブンな待ち受け構文

言語の基本機能ではなくコアモジュールの機能ではあるのですが、いわゆるwhileループの代わりになるのがasync.stayです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
use core.io!;
use core.schedule!;
use core.async!;

// async.stayでwhileループのように待ち受ける。waitは状態変数、5000が初期値。
result = async.stay(wait: 5000) {
src = schedule.timeout(wait); // <-- 5秒経過したらsrcがsuccessとなるか、どこかのタイミングでfailureとなる。
handler = async.expects(ev => async.leave("completed"), src); // <-- srcを監視し、trueになった瞬間にasync.leaveする。
handler.ready() // <-- srcを監視し始める。
};

// resultに結果が入っているので、出力する。
result !? {
success(v) => io.println("Result: " + v)
failure(e) => io.println("Error: " + e)
};

上記の例ではasync.staywhileループのような役割を行っています。async.stayブロックには引数としてkey: valueの形式で複数の値を状態変数として渡すことが可能です。

なお、ひとつのasync.stayに対して複数のasync.expectsを設定することが可能です。async.leaveがコールされた時点で、特段の指定がなければasync.stayは処理を終了します。

複数のイベントをこの構文で待ち受け可能ということです。

Calciumの現状

はっきり言ってまだ作り始めてから数日ですから、粗もバグもあると思います。しかし、いったん動作させることには成功していると言える状態には持ってきたと思います。

また、エコシステムも現状開発中でして、近いうちにモジュールレジストリboneyardとモジュールマネージャーboneが連携して動作するようになる予定です。

バイトコードへのコンパイルとバイトコードの実行は現時点で概ね動作していますが、外部モジュールのバンドル等についてはまだ現時点では実装ができていません。

まとめ

ひとまず、AIに色々指示を出してCalciumを作っている、という状況です。また進展があったらエントリを書くと思います。気長にお待ちいただけますと幸いです。

AirGit 0.0.3 has been released

いつもお世話になっております。わいとん(@ytnobody)です。

今年もいよいよ終わりが近づいてきましたね。私はいま、お花摘みを我慢しながらこのエントリを書いています。

さて、この度私はAirGitというソフトウェアを考案・公開しましたので、お知らせいたします。

AirGitというものを作りました。

AirGitというのは、ブラウザから直接Gitリポジトリを管理できる軽量なWebベースのGitクライアントとなります。push、pull、branch作成、remote管理など、モバイルフレンドリーなUIで操作可能となっているのが特徴です。

リモートホストにあるGitレポジトリたちを、スマートフォンからブランチ操作をしたり、pushしたりタグを作ったりしたいなー、という要求を満たすために作りました。

AirGitの特徴とできることは以下の通りです。

  • ワンタップ操作を重視したモバイル最適化UI
  • 複数リポジトリ管理(ローカルファイルシステム)
  • ブランチ完全管理(一覧表示、作成、チェックアウト、削除)
  • Gitタグ管理(一覧表示、作成、プッシュ)
  • Git操作(プッシュ、プル、ステータス確認)
  • リモート管理(追加、更新、削除、選択)
  • リポジトリの初期化と作成
  • コミットの先行/遅延追跡
  • モバイル最適化UI(下部ナビゲーションバー)
  • PWAサポート(オフラインキャッシュ、ホーム画面アイコン)
  • コミット履歴表示(過去20件)
  • User-mode Systemdサービスへの登録と管理
  • 設定メニューによる構成

(AirGitの見た目。きわめてシンプル。)

正直なところ、Ubuntu Linux以外での動作はまったく確認しておりませんので、何か問題があればIssueでお知らせください。

Goで作られていますから、リリースバイナリをDLし、実行するだけでアプリケーションが起動します。

amd64なCPUのLinuxの場合は…

1
2
$ chmod +x airgit-linux-amd64
$ ./airgit-linux-amd64

これだけで http://localhost:8080 が立ち上がるようになっています。

vibe-kanban + tailscale + AirGit = スマホだけでvibe coding

AirGitは、その真価をvibe-kanbanおよびtailscaleと組み合わせたときに発揮できるものとなります。

vibe-kanbanは、カンバンのような見た目のボードでvibe codingを行うツールです。vibe-kanbanにレポジトリを追加し、カンバンに新しいタスクを登録すると、AIがコードを仕上げてくれるという優れもの。ちょっと前までは結構不具合も多かったのですが、ここ2か月くらいで安定して使えるようになった感があります。

但し!vibe-kanbanだけではgit pushをはじめとしたgitレポジトリの操作ができないのです…(仮にできたとしてもボタン一発、とはならない)。

そこでAirGitを使って、スマホからgit pushしてやろうぜ、という目論見です。

(vibe-kanbanのカンバンボード。AirGit開発中のもの。)

一方、tailscaleはVPNを構築するためのツールであり、iOS, Android, Linux, Windows, MacOSなどなどに対応しています。

つまり、vibe-kanban、AirGitを開発マシン(Linux)に立ち上げておき、tailscaleを開発マシンとお手持ちのスマホに入れておけば…スマホだけでvibe codingが可能な環境を作り上げることができちゃう、というわけです。

AirGitはvibe codingの産物

ところでもうすでにお気づきの人もいると思いますが、AirGitはvibe-kanbanによるvibe codingだけで作られています。

途中からAirGitそのものを使ってレポジトリ管理をしていますから、実際のところはドッグフーディングしながら作ったことになります。

その気になれば、AirGitにvibe codingの機能を組み込むことも可能でしょう。今のところ、そこまではやろうと思っていませんが。

なおすでにvibe-kanbanには2つのissueを起票しておりまして、vibe-kanbanでgitレポジトリの操作ができるようになれば、AirGitはお役御免になると思います。が、そこまで待ってられないので、AirGitを作ったんです。

まとめとか雑感とか

AIにGo書かせるのいいですね。TypeScriptよりも正確かつ実用的なものが素早く安価に得られていると思います。

今後、こういう感じで自分が欲しいツールは小さくまとめてAIに作らせていく、というのが開発スタイルの主流になりそうな気がします。

AirGitについては、もし試しに使ってみたという方がいれば、感想や要望などお聞かせいただけると嬉しいです。

MCPとかTOONとか

いつもお世話になっております。わいとん(@ytnobody)です。

さて、ただいま宿泊先のホテルのロビーでほろ酔い気分でこのエントリを書いています。Perl Advent Calendar 2025のday 6となります。

みなさんはMCPサーバーというものを使ったことはありますでしょうか?AIにコードを書かせているような人は間違いなく使ったことがある側の人だとおもいます。

ではMCPサーバーを作ったことはありますか?私はこのエントリを書くまではまったくありませんでした。もっぱらMCPサーバーについては使うばかりでした。

しかし、なんとPerlでもMCPサーバーが書けるということらしいです。どうにか私もMCPサーバーをPerlで書きました。正確には私がAIに指示を出して書いてもらったのですが…

MCPサーバーをPerlで作るには

実はPerlでは非常に人気の高いWebフレームワークとしてMojoliciousが挙げられます。この作者であるSebastian Riedelさんによって、その名もずばりMCPというモジュールが作られています。

Mojoliciousで/mcpパスをMCPサーバーとして公開する場合、以下のようにすればよいようです(SYNOPSISそのまま)。

1
2
3
4
5
6
7
8
9
10
11
12
13
use Mojolicious::Lite -signatures;
use MCP::Server;
my $server = MCP::Server->new;
$server->tool(
name => 'echo',
description => 'Echo the input text',
input_schema => {type => 'object', properties => {msg => {type => 'string'}}, required => ['msg']},
code => sub ($tool, $args) {
return "Echo: $args->{msg}";
}
);
any '/mcp' => $server->to_action;
app->start;

なんか思ったより簡単そう。でも、そもそもHTTPでの通信すら面倒だからSTDIO(標準入出力)でやり取りさせたい。という場合にはSTDIOトランスポートを使うとよいのだそう(SYNOPSISママ)。

1
2
3
4
5
6
7
8
9
10
11
12
use Mojo::Base -strict, -signatures;
use MCP::Server;
my $server = MCP::Server->new;
$server->tool(
name => 'echo',
description => 'Echo the input text',
input_schema => {type => 'object', properties => {msg => {type => 'string'}}, required => ['msg']},
code => sub ($tool, $args) {
return "Echo: $args->{msg}";
}
);
$server->to_stdio;

これならHTTPで公開するわけじゃないから、手元で試すのに便利そうです。

さて、これを参考にClaude Haiku 4.5にオレオレMCPサーバーを作ってもらうことにします(成果物はこのあと紹介)。

MCPクライアントはどうするか

色々調べてみたんですが、やれClineをつかえだの、やれClaude Codeがいいぞだの、色々あるっちゃああるんですよ。

でも、これもPerlで作ったらいいじゃないですか。雑なMCP ClientをPerlで作ってはいけないという決まりはありません。だいたい、ツールを増やしたいわけではなく、自作MCPサーバーとやり取りできるクライアントが欲しいだけなんです。無料でアカウントを作らせようとするおせっかいはここでは不要なんですよ。

で、MCP::Clientというモジュールもあるにはあるのですが、これはHTTPベースのトランスポートしかサポートしておらず、STDIOトランスポートは未サポート。

仕方がないので、これもClaude Haiku 4.5に作ってもらうことにしました(このあと成果物を紹介)。

そういえばちょっと前にTOONというものが話題になったな?

何週間か前くらいにTOON(Token Oriented Object Notation)というデータフォーマットがにわかに話題になっていました。

いわく、LLMのトークン削減に効果的な構造化データフォーマットだー、みたいなことが特徴らしいのですが、まあ良く見ると、なんとPerl向けモジュールがあるとのこと。

老後に趣味のPerlを盆栽のように楽しみたい者としてはニッコリな状況というわけですが、ああ思い出した、これは私が「Perlモジュールがない」という理由で高血圧をこじらせてしまいそうだったので、慌ててClaude haiku 4.5に作らせたのでした。ここでこのData::TOONを作っていなければ、大好きなサウナをまた我慢しなければならないところでした。

ちなみに使い方はこういう感じ(SYNOPSISママ)です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
use Data::TOON;
# Basic usage
my $data = { name => 'Alice', age => 30, active => 1 };
my $toon = Data::TOON->encode($data);
print $toon;
# Output:
# active: true
# age: 30
# name: Alice
my $decoded = Data::TOON->decode($toon);
# Tabular arrays
my $users = {
users => [
{ id => 1, name => 'Alice', role => 'admin' },
{ id => 2, name => 'Bob', role => 'user' }
]
};
print Data::TOON->encode($users);
# Output:
# users[2]{id,name,role}:
# 1,Alice,admin
# 2,Bob,user
# Alternative delimiters
my $encoder = Data::TOON::Encoder->new(delimiter => '|');
print $encoder->encode($users);
# Or with tabs:
my $tab_encoder = Data::TOON::Encoder->new(delimiter => "\t");
# Root primitives and arrays
print Data::TOON->encode(42); # Output: 42
print Data::TOON->encode('hello'); # Output: hello
print Data::TOON->encode([1, 2, 3]); # Output: [3]: 1,2,3

全部混ぜてつくってみた

という、ここまで紹介した要素を全部混ぜてつくってみたのがこちらのレポジトリです。
ざっくり箇条書きで説明するとこんな感じ。

  • MCP::Serverを使ったSTDIOトランスポートなMCPサーバー mcp_server.pl
    • MCPサーバーに必要な基礎機能を全部実装。
    • 足し算、エコー、pingをツールとして実装。
  • まあまあ自力(ではなくAIが全部)実装なMCPクライアント mcp_client.pl
    • クライアントと人間の間のデータ入出力インターフェースはTOONフォーマット。
    • MCPサーバーとのやり取りはただのJSONでやり取り(普通のMCP)。

TOONの意味ねえ!とか、ほかにもツッコミどころだらけではあるんですが、こういうこともできるんすよ、Perlで。って言いたいだけなんで、そこんとこよろしくお願いしたいところであります。

さいごに

MCPで肉体を若返らせることはできませんでしょうか?できませんか、そうですか…

Perlからでもtursoを使いたい!

いつもお世話になっております。わいとん(@ytnobody)です。

さて、ただいま羽田空港の待合所でこのエントリを書いています。Perl Advent Calendar 2025のday 4となります。

内容については、タイトルの通りです。で、それを実現するモジュールを作りました。作ってしまえば、できるのです。

tursoってなんだ?

tursoというのはDB as a Service(DBaaS)の一種です。個人開発勢・小規模開発に特におすすめです。

詳しい特徴等については以下のエントリがよくまとまっていると思います。

ざっくりいうと、めちゃくちゃ安い、SQLiteっぽい、エッジネイティブDBサービスってところでしょうか。なお、正しくはSQLiteではなくlibsqlというもののDBaaSということになるようです。

あと便利機能として、DBスキーマのブランチとマージ機能があるので、Planetscaleっぽくgithubフローに乗ったスキーマ運用が可能だったりします。

今回つくったものたち

で、このtursoですが、公式にはPerlからの接続は基本的にできません。ライブラリの提供もありませんし、ドライバもです。

Perlを老後も趣味で触りたいものとしては、この仕打ちには我慢なりませんでした。このままではただでさえ高い血圧が余計に上がってしまう…

従いまして、これ以上血圧を上げないようにするために以下のモジュールを作りました。

Alien::Turso::CLI

これは要するにtursoコマンドを使えるようにするためのモジュールです。それだけなら、特段このモジュールの必要性はないと思いますが、DBD::libsqlを作るにあたり、ローカルで簡単にturso devを実行できる必要がありました。

このturso devはtursoにアカウントがなくても、とりあえずローカルでtursoサーバーを起動するコマンドです。よって、これを使えばテスト時にモックを作る必要がないのです。

DBD::libsql

さて、これが今回の主役です。

使い方はmetacpanのsynopsisに書いてあるのですが、まあ同じものをコピペしておきます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
use DBI;
# Connect to a local libsql server
my $dbh = DBI->connect('dbi:libsql:localhost', '', '', {
RaiseError => 1,
AutoCommit => 1,
});
# Connect to Turso with authentication token (recommended approach)
my $dbh = DBI->connect(
'dbi:libsql:my-db.aws-us-east-1.turso.io',
'', # username (unused)
'your_turso_token', # password field used for auth token
{
RaiseError => 1,
AutoCommit => 1,
}
);
# Alternative: Turso connection with connection attribute
my $dbh = DBI->connect('dbi:libsql:my-db.aws-us-east-1.turso.io', '', '', {
RaiseError => 1,
AutoCommit => 1,
libsql_auth_token => 'your_turso_token',
});
# Create a table
$dbh->do("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)");
# Insert data
$dbh->do("INSERT INTO users (name) VALUES (?)", undef, 'Alice');
# Query data
my $sth = $dbh->prepare("SELECT * FROM users WHERE name = ?");
$sth->execute('Alice');
while (my $row = $sth->fetchrow_hashref) {
print "ID: $row->{id}, Name: $row->{name}\n";
}
$dbh->disconnect;

mysqlやpostgresqlなんかとあまり変わらないと思いますが、接続時にusername相当が空文字だったり、パスワードにtursoトークンをつかうなど、ちょいちょい違う点があります。

でも、それ以外は他と一緒です。拙作のORMもどきであるOtogiriとも組み合わせられますし、他のORMでも使えると思います。

さいごに

最近かいたモジュールはほとんどClaude Sonnet 3.5かClaude Haiku 4.5に書かせておりまして、私自身は設計こそしていますが、細かいコード実装を書いていません。それでもこれだけのモジュールを作ることができますし、AIを使うことを躊躇してる場合じゃない、と思う次第です。

ちなみに、このモジュールはYAPC::Fukuoka 2025でkobaken(@kfly8)さんによって作られ提供されていた「令和最新版Perl掲示板」にて使用されていました。

YAPC::Fukuoka 2025で共同登壇&スタッフをしました

#yapcjapan タグを見てくださいよ。めちゃくちゃ盛り上がりましたね、YAPC::Fukuoka 2025!

そんなわけで、まずスタッフと福工大の山澤先生、スポンサーのみなさん、参加者のみなさん、本当にありがとうございました&お疲れさまでした!!

こばけんさん (@kfly8) と共同登壇しました

Perlの生きのこり というタイトルで、こばけんさんと共同登壇しました。

この発表自体は きのこカンファレンス2025 でやったものではあるのですが、YAPC::Fukuoka 2025向けにかなり研ぎ澄ましておりまして、より本質的な発表となったかと思います。登壇資料についてはこばけんさんから共有があると思いますので、それをお待ちいただきたいと思います。

追記:直後にスライドの共有をいただきました!こばけんさんありがとうございます!!!

きのこカンファレンスの時と違ったのは、漫談風にしながらももう少し変化の歴史と最新の書き方にフォーカスを絞った、ということと、私自身が素面で登壇した(ここ重要)という点でしょうか。

CGIってなんなの?とか、そもそもlighttpdっていうサーバーがあったんだ、みたいな過去の経緯があって今に至っているんだなぁということ。そして 令和最新版Perl BBS を通じて、今どきのPerlってこう書くのか、というのを観測いただけたら幸いです。

あと、資料ぜんぶこばけんさんが書いてくれたので、感謝しかありません。ありがとうございました!

(令和最新版Perl BBS動作の様子)

「Sledgeについて触れないのはいかがなものか」

そういえば@ssig33さんから「Sledgeについて触れていないのはいかがなものか」という旨のご意見をいただいていましたが、あれは大まじめに入れたくても入りきらないというものだったので、どうか勘弁してくださいって会場で半土下座させていただきましたね。

歴史について深堀りするなら、間違いなくSledgeについては避けて通れないとは思いますし、SledgeがあったからRuby on Railsのようなフレームワークが出てきたんだよね、ということについては間違いなくその通りだと思います。2000年代初頭にPerlが絶大な人気を誇っていた理由のひとつであると言っても過言ではないでしょう。

せめてここで参考リンクを紹介するという形で、落穂ひろいとさせていただければと思います。

まず開発元のlivedoorによるGitHubレポジトリがありますので、入手はこちらから可能です。歴史を感じることができると思いますので、ぜひ見てほしいです。

現代においては、@karupaneruraさんによる下記ブログエントリが参考になると思います。

また日経クロステックには、Sledge作者のひとりの@miyagawaさんによる案内記事が残っていましたので、こちらもあわせて紹介いたします。

Track Cの番長やってました

もうひとつ、今回はスタッフとしての参加でもありました。基本的にはTrack Cに張り付いて場を管理する役割を帯びておりまして(通称「番長」)、副番長の @chanyou さんに大きく支えられながら、なんとか無事に役割を全うすることができました。部屋付きのスタッフの皆さんにも大変助けられました。ありがとうございました!

さて、そのような都合もあって、実はあまりTrack C以外のトークをみることができていないのですが、その中でも私が感心してみていたのは以下のトークでした。

なぜインフラコードのモジュール化は難しいのか - アプリケーションコードとの本質的な違いから考える by 宮下 剛輔 | トーク | YAPC::Fukuoka 2025 #yapcjapan - fortee.jp

私自身がTerraformを最近ようやく触ることがあったのですが、この発表では私がTerraformになんとなく感じていたモヤモヤの原因が解像度高く言語化されておりましたね。

あのモヤモヤ対する適切な対処がきっちりと提示されつつも、ほどほどにやっていくことの大切さが語られていました。行き過ぎた最適化は負担となりうるということを示してくれた、貴重な発表でした。

Amazon ECSデプロイツールecspressoの開発を支える「正しい抽象化」の探求 by 藤原俊一郎 | トーク | YAPC::Fukuoka 2025 #yapcjapan - fortee.jp

こちらも実は最近ようやくECSを触る機会があったのですが、あの若干行き届いていない感じをecspressoがうまくいなしてくれるんだな、と理解しました。

また、その開発にあたっての設計哲学と「あえて設計しない」というやり方について、凄みを感じましたね…つい設計しちゃいそうなところだと思うのですが、そこは熟練の肌感なんだろうなあと。本当に感心します。

飛行機の都合で懇親会の途中で帰ることに… 😭

ところで、私はケチな旅行が大好きです。飛行機代をどうすれば圧縮できるのか。電車賃は?宿代は?もちろん、豪華なホテルもいいと思いますし、私もごくまれに値段を気にせずに旅をすることがあります。しかし、普段は超ドケチスタイルでやらせてもらっています。

で、今回はANAのセールでチケットを購入しつつ、ホテルもカプセルホテルを押さえるという形で対応しました。移動と宿泊についての費用としては以下の通り。

  • ANAセールによるチケット
    • HND-FUK:片道7,700円(税込)、往復15,400円
  • ファーストキャビン博多
    • 1泊あたり3,630円(税込)、2泊で7,260円
  • 福岡市営地下鉄1日乗車券
    • 640円
  • そのほか交通費
    • 2,800円
  • 合計:26,100円

それなりにドケチ旅行と銘打つことが許される価格帯ではないかと思うのですが…どうでしょうかね。

ところが、土曜日の宿泊と日曜日の航空券がどうあがいても取れない!!!ということで、泣く泣く懇親会を中座するという羽目になったのでした…いやあ、これは反省です。

さいごに

YAPC::Hakodate 2024で実行委員長をしたときびきニキさんにYAPC::Fukuoka 2025をバトンタッチしたのですが、まじですごいの一言に尽きます!

懇親会のごはんがめちゃくちゃおいしいし、ヘルプページの整備という形で案内をまとめたのはさすがだなあと。#yapc_memorial の提唱と盛況ぶりは彼女が呼びかけなければ起こりえないことです。実行委員長という大役、お疲れ様です!ありがとう!

ところでこれは余談なのですが、「次回はビッグサイトだ」と聞いたので、真っ先に目の前にいた@dankogaiさんに「海外からもスピーカーを呼びましょうよ!」と、半ば強請りのような勢いで懇願に行ったのは体が勝手に動いてしまった結果です。

でも、当時のYAPC::Asiaを追い越すイベントを一緒にやりませんか、弾さん!

The Essential System Layers(本質的システム階層)

混沌とするシステム技術の世界における羅針盤

AIの台頭、ローコード/ノーコード(LCNC)の急速な普及、そして絶え間なく進化するフレームワークやインフラ技術。現代のソフトウェア開発を取り巻く環境は、まさに「混沌」と表現するのが相応しいでしょう。新しい技術が次々と登場し、あっという間に古くなる、そんな変化の波に乗り遅れまいと、多くのエンジニアが表面的なトレンドに目を奪われがちです。

長年のキャリアの中で、私は「技術は常に変化する」という現実を肌で感じてきました。しかし、だからこそ、目先の技術に囚われず、「本質」を見極め、変化の激しい時代を生き抜くための確固たる思考の軸が必要だと強く感じています。

このエントリでは、私自身の経験と試行錯誤の中でたどり着いた、システム構築における「The Essential System Layers(本質的システム階層)」をご紹介します。これは、技術を単なるツールの羅列としてではなく、それぞれの層が論理的に連携し、システム全体を構成する「必須」の要素として捉えるための、私なりのメタ認知フレームワークです。

「The Essential System Layers」が示す、システム構築の7層構造

私の提唱する本質的システム階層は、システムの根源にある「なぜ作るのか」という問いから、ユーザーが実際に触れる「最終的な成果」に至るまでを、段階的かつ相互依存的な7つのレイヤーとして表現します。各層は「選択」と「行使」のプロセスを内包し、全体として一貫したシステム構築の流れを示します。

第0層: 根源層(Root Layer): 仕様 & ビジネスドメイン

この層は、システムが作られる以前から存在する、システム構築の「前提」となるものです。7つのレイヤーに含めることで、技術的な選択が常にビジネスの目的と要件に基づいて行われるべきであることを強調しています。

  • 役割: システムの存在意義と目的を定義する、すべての出発点。「何を(What)解決すべきか」「なぜ(Why)それを作るのか」を明確にします。技術的な制約から独立した、純粋なビジネス上の要件、顧客の課題、市場のニーズ、そして最終的なビジネス価値がここに集約されます。

第1層: 論理設計層(Logical Design Layer): ドメインモデリング & アーキテクチャ設計

  • 役割: 第0層のビジネス要件を受け、それを技術的に実現可能な「論理的な形」に落とし込む層です。ビジネスルール、データ構造、システム全体の振る舞い、主要コンポーネント間の関係などを、具体的な技術実装に依存しない形で設計・定義します。システム全体の骨子を決定する、最初の「選択」が行われる場です。

第2層: 実装手段選択層(Implementation Means Selection Layer): 開発パラダイム & 技術スタック選定

  • 役割: 第1層の論理設計を実現するための、具体的な開発手法や主要な技術スタックを決定する層です。開発速度、コスト、複雑性、保守性などを考慮し、最適なアプローチを選定します。

    • AI(コード生成、自動化エージェント)ローコード/ノーコード(LCNC)ツールは、特定のビジネスロジックや定型処理を迅速に実装する際の強力な選択肢です。ただし、この層における安易な選択は、後に不必要なコストや非効率を招くこともあります。
    • 各種プログラミング言語(Python, TypeScript, Goなど)は、より複雑なカスタムロジックや、性能が要求される部分に選択され、きめ細やかな制御を可能にします。

    AIの位置づけについて: 近年注目を集めるAIは、このレイヤーにおいては、あくまで「実装手段の選択肢の一つ」として捉えられます。AIは、特定のタスクを自動化したり、コードを生成したりする強力なツールですが、システムの根本的な設計やドメインモデリングに直接的な影響を与えるものではありません。

第3層: コード・ロジック層(Code & Logic Layer): ソースコード & 個別ミドルウェア実装

  • 役割: 第2層で選択された手段を実際に「行使」し、第1層で定義された論理設計を具体的なコードや設定として表現する層です。

    • プログラミング言語で記述されたアプリケーションコードや、その開発を効率化するフレームワーク(React, Spring Bootなど)がここで活用されます。
    • LCNCツール上での設定や、AIの挙動を制御するプロンプト/モデルの定義、さらにはデータベースのスキーマ定義など、各ミドルウェアがアプリケーションロジックと連携するために必要な具体的な実装や設定もこの層で行われます。ここが、「実装」の主戦場です。

第4層: 実行環境層(Execution Environment Layer): 実行エンジン & コンテナ技術

  • 役割: 第3層で作成されたコードや設定が実際に「動く」ための、抽象化された実行環境を提供する層です。

    • Java Virtual Machine (JVM) や Node.js (V8) といった言語ランタイム、そしてDockerやKubernetesなどのコンテナ技術が、アプリケーションの動作環境を標準化し、隔離します。ここも、「実行環境の選択と設定」が行われる場です。

第5層: インフラ基盤層(Infrastructure Foundation Layer): クラウドインフラ & 仮想化

  • 役割: 第4層の実行環境を稼働させるための、仮想化された、あるいは物理的な基盤を提供する層です。

    • AWS, Azure, GCPなどのIaaSが、サーバー、ネットワーク、ストレージといったITリソースを抽象化して提供します。物理サーバーや仮想化技術もここに属します。

第6層: 物理・制御層(Physical & Control Layer): ハードウェア & オペレーティングシステム (OS)

  • 役割: すべてのデジタルシステムが最終的に依存する、最も低レベルな物理的・論理的基盤です。

    • CPU、メモリ、ストレージなどのハードウェアと、Windows、Linux、macOSなどのオペレーティングシステム(OS)が、上位層のソフトウェアを支える最終的な土台となります。

第7層: 出力・ユーザー体験層(Output & User Experience Layer): 具体的な機能・ユーザー体験

  • 役割: システムが最終的にユーザーに提供する価値、インターフェース、そしてそれによって生まれる体験の層です。

    • 第0層のビジネスドメインが、すべての層を経て具体化され、ユーザーが直接触れ、認識し、利用する最終的な機能やインターフェースがここに現れます。

「The Essential System Layers」がエンジニアにもたらす価値

この本質的システム階層は、単なる技術の分類に留まりません。複雑なシステム構築において、エンジニアが確かな視点を持つための強力なツールとなります。

  1. 適切なツール選択の指針:
    各層の役割と特性を理解することで、表面的なトレンドに流されることなく、目の前の課題にとって本当に最適な技術やアプローチを選択できるようになります。AIやLCNCを過大評価することなく、また従来のプログラミング言語の重要性を見失うことなく、バランスの取れた判断が可能になります。

  2. 問題の特定と解決能力の向上:
    システムで問題が発生した際、それが「仕様理解の不足(第0層)」に起因するのか、「設計の誤り(第1層)」なのか、「手段選択のミス(第2層)」なのか、「コードの実装バグ(第3層)」なのか、あるいは「実行環境やインフラの問題(第4層~第6層)」なのかを、この階層構造に当てはめて素早く特定し、効率的に解決に導くことができます。

  3. キャリアパスの明確化と「本質」を見抜く力:
    自身のスキルがこの構造のどの層に位置し、今後どこを深めるべきか、あるいは広げるべきかを戦略的に考えるための羅針盤となります。技術の表面的な変化に惑わされず、各層の「本質」的な役割を理解することで、エンジニアとして長期的に価値を生み出し続ける力を養うことができます。

  4. 新しい技術の本質とその影響を素早く把握する力:
    昨今のAIやLCNCのように、新しく登場した技術がどの層に位置するのかを見極め、それによりどんなことを解決してくれるのかをスムーズに理解するための一助となります。また、新しい技術が自身のフォーカスする層にどのような影響があるのかを理解し、どのくらいの速度で変化していくのかを見定めるためのツールとなります。

変化の時代を生き抜くエンジニアとして

テクノロジーの進化が加速する現代において、エンジニアはますます複雑な課題に直面します。しかし、このように体系化されたメタ認知を持つことで、私たちは単なる「技術の利用者」から、その「技術を真に理解し、使いこなす設計者」へと進化できるはずです。

例えば、AI技術の進歩と普及は一見何もかもを覆してしまったように見えるかもしれません。しかし、このメタ認知に当てはめて考えるならば、AIは主に第2層(実装手段の選択)に関わる技術であり、システムの根本的な設計やドメインモデリングに直接的な影響を与えるものではない、と冷静に判断することができます。

この本質的システム階層というフレームワークが、あなたのシステム開発における意思決定、問題解決、そしてキャリア形成の一助となれば幸いです。

[Event]NT函館2025で出展した話

NT函館ってなんだ?

NT函館 というイベントをご存じない方のためにかんたんに説明すると、だいたい以下のような感じです。

  • NT = Nanka(なんか)Tsukuttemita(つくってみた)の略とのこと。要するに、ものづくりに関する展示イベントになります。
  • 趣味でつくってみたものや自分の好きに作ってみたものが一斉に集まり展示され、さまざまな技巧・趣向が凝らされた作品がおよそ55点展示されました。
  • ソフトウェア・ハードウェア、やたらデカいロボットや座禅訓練マシーン、公道走行可能なミニカーなど、良い意味でイカれてる作品ばかり。

NT函館のほかにもNT東京、NT札幌、NT金沢などがあるようです。

すごい展示たち

ひとまずこいつらを見てくれ!すごい!すごすぎる!

自動演奏する鉄琴。下にコイルと鉄くぎで作った「ハンマーユニット」がずらりと並んでおり、あらかじめ設定された楽曲を奏でる。

レーザー電子ハープ。レーザーを手などで阻害すると(手は焼かれずに)音がなる。テクノっぽい。

座禅訓練マシーン。座面に感圧センサーが数か所設置されており、姿勢が揺らぐと「雑念がある」とみなして喝を入れられる。

他にもたくさんありました。

私の展示

私は「ハコダテ縦断ウルティメイトクイズ」というものを展示しました。なんとHSP3で作った雑なコードでありますが、とりあえずエンタメに振り切ったものとしました。

ただ開発期間が短く、バグ取りまではしきれなかったですね。

ちなみに、ある程度クイズを勝ち上がった方には「ニューヨク行きチケット」をお配りしました。良い湯だな~ってなってくれたら幸いです♨

感想

月並みかもしれませんが、まぁめっちゃ楽しかった~!時期にもよるけど、次はもっとものづくりに振った展示を出したいな~!と思った次第でした。

[Event]きのこカンファレンス2025に参加した話

きのこカンファレンスこと「エンジニアがこの先生きのこるためのカンファレンス2025」に参加してまいりましたので、そのレポートです。

前夜祭

前夜祭では「Perlの生きのこり ~ペアプロ解説~」と題しまして、kobakenさん(@kfly8)との共同登壇をさせていただきました。

事前に企画に関するエントリをnoteでも公開してあったのですが、一部の方々からの期待値が結構高まっていたようでして、盛り上げ役としては絶対に面白くしてやるぞ、という覚悟をもって登壇に臨んだ次第です。

詳しい発表内容についてはkobakenさんのエントリに委ねるとして。

楽しかったに決まってますよ、最高でした!>@kfly8

そしてこのような機会をいただけて、本当にありがたいことです。とにかくPerlの歴史を楽しく伝えつつ、今のPerlの様子を見てもらいたい、という狙いは達成できたかな、と思います。欲を言うならば、今度はこれを2時間くらい使ってもう一度やりたいですね!

とにかく前夜祭は大盛況でした。雰囲気を伝えるべく、Xをペタペタと貼っていきます!(引用させていただいたツイ主の皆さま、NGでしたら わいとんに遠慮なくお申し付けください。「許可を求めるな謝罪せよ」のスタンスでやらせていただいております!)

YAPC::Fukuoka 2025 の宣伝

マジでみんな来てくれ!!!!

みんなPerlやってるかい!?

だって「やってる!」って返事が帰ってこなかったもんだから・・・

CGI、Lambdaに似てないっすか?

いやLambdaっていうかFaaSがCGIっぽいんですよ?

令和のPerl

若干ほかの言語に見えなくもない

Claudeはラクダの夢を見るか

AIとPerlは親和性高いって私は思っていますよ

酒カスムーブ

最後に残ったわずかな獺祭の処分に運営さんがちょっと困っていたのでつい・・・

当日

当日は午前中に野暮用を済ませる必要があり、午後からの参加となりました。

前日夜にスマホを充電し忘れたまま寝てしまい、バッテリー残量の都合上、あまり積極的にツイートできなかったのですが、まず感想を率直に表すと、めっちゃ楽しかったです!

印象深かったトーク

  • 「働きママエンジニア」、看板もそろそろ畳みます! 無冠のわたし、これからどう先生きのこれる? - たかのあきこ
    • まずスライドが全部手書き、しかも筆ペンか毛筆のどちらかで書かれていて、見ていてとってもほっこりするデザインでした。
    • 「働きママエンジニア」としての状況の変化と、それに合わせてキャリアのチャレンジの枠をどんどん広げていく様子、そして最後に「はたらくマダムエンジニア」としてのあゆみを進める、というストーリーラインがとても素敵なものでした。
  • フルリモートでも生き残る。たとえ日本中からフルリモ案件がなくなったとしても。 - 株式会社Sanukite(サヌカイト) 代表取締役 水尾峻輔
    • フルリモートのお仕事そのものがコロナ禍のころと比較して激減した、というものの、コロナ禍以前はもっと状況は悪かったし、そういう意味では今は低空飛行で今後も層だろう、との見通しは、だいぶ似たようなことをやっている経営者として、共感を通り越して一種のシンパシーを感じました。
    • リーダー経験、顧客折衝あたりができるとリモートでは強い、というのも「その通り!」と思いながら聞いていました。共感できる点が大変たくさんあったトークでした。
  • AIが台頭する時代のエンジニア進化論:価値探索とAI-Human Interfaceのスペシャリストへ - おだかとしゆき
    • AIが生み出す価値と張り合うのは人間には無理で、AIではできない部分、つまり最終的な判断、とくに野性に基づく判断ができるのは人間にしか為しえないというのはなかなか面白い考え方だなと思いました。困難な状況に対応し生き抜くための力としての野性は、確かに人間じゃないと出来なさそうですよね。
    • ある種、ハードシングス的な考え方がAIとは違った人間ならではの本領なのかもしれませんね。
  • 変わりゆくもの、変わらないもの。 - 米久保 剛
    • ゲームチェンジャーとしていくつかのIT技術は、これまでおおむね10年おきに登場してきましたが、そのたびに変容をもとめられるのがエンジニア。でも、そんな中でも変容しないものが厳然として存在しますよね、というお話でした。
    • とくに具体→抽象→高度な抽象→別の具体、というような、具体と抽象の間を意識的に行ったり来たりするというのをすることが「変わらないもの」を体得する上で重要である、というのは印象深かったです。

スマホのバッテリー10%前後で写真撮ったりしてた

天気がよいときの台場は本当に景色がいいんですよね。

ここでもしっかりとYAPC::Fukuoka 2025を宣伝。

みんなの歴史年表を見て思ったこと

懇親会、アンカンファレンス、その他

  • カンファレンスの食事は本当に大変。何が大変って量のコントロールが大変。
  • カンファレンスのゴミ処理問題、それを解決するだけのビジネスを立ち上げたら需要はあるのではなかろうか?儲からなさそうだけど。
  • レアなキャリアを突き詰めていくと、職業名が自分の名前(もしくは自分のハンドル名)になる未来しか想像できない。
  • 人は、好きなことで生きていきたいから起業するけど、好きなことにはやりたくないことも一緒についてくる。特盛カツカレーに例えると、好きなことは福神漬け程度の量で、残りの特盛カツカレーくらいの量のやりたくないことがついてくるものだ。
  • やりたくないことを淡々とこなせるのがプロ
  • エンジニアが経営をすると、数字からは目をそむけたくなる。プログラムは書くのに数字はダメなんですねーって言われがち。
  • すごい数の豪華日本酒が持ち込まれた。が、私は前夜祭でやたら飲みまくったので、懇親会ではアルコールレスアーキテクチャを貫徹しました。

さいごに

スポンサー各社のみなさん、スタッフのみなさん、そしてスピーカーのみなさん、一般参加のみなさん、本当にお疲れさまでした。そしてありがとうございます。

きのこカンファレンス、最高の体験でした!ちょっと見た目には毒気がありつつ、でもふんわり甘い香りでおいしくて、食べると気持ちよくなっちゃうきのこ、という感じで(めっちゃ褒めてます)大変良かったです!

生きのこりについて思いを馳せながらこのエントリを書きました!本当にありがとうございました!

[Perl]他の言語にあるアレをもって来ようぜって話

このエントリはPerl Advent Calendar 2024の22日目のエントリとなります。

他の言語にあるアレとは

皆さん、Perl触ってますか?Perlを触っている方もそうでない方も、ご自身がよく使う言語にあるメジャーなライブラリや大変有用な基礎機能というものがあると思います。私が思いつくところですと、例えば以下のようなものでしょうか。

  • C#におけるLINQ
  • F#におけるパイプライン演算子
  • Goにおけるgoroutine
  • RubyにおけるMix-in
  • ElixirにおけるLiveView
  • PHP/LaravelにおけるLivewire
  • などなど・・・

とにかく、いくつかの言語で実装されている便利なやつ、ということを言いたいのです。

で、最近私はTypescriptという言語を触る機会が多いのですが、この言語では大変有用なライブラリがたくさん作られ、提供されています。一部をご紹介します。

  • Prisma ORM : Typescript向けに作られたORM。スキーマ変更に対するケアが素晴らしい。
  • Zod : データバリデーター。非常に複雑なデータ構造に対するスキーマをシンプルに記述できる。
  • Biome : ソースコードのフォーマッティングと構文解析を行うツール。品質の高いコードには必須。

Perlにも便利なものを持ってきてもいいじゃないか

近ごろは、Perlにとって明るい話題というのをなかなか聞かなくなってきたなぁと思うようになりました。個人的にはPerlは好きな言語ですし、私がまともにITエンジニアとして食っていけるようになるきっかけとなった言語ですので、末永く便利に使いたいものだと思っています。

そんなことを考えていたある日、先ほどご紹介したZodというバリデーターの存在を知り、業務でも利用するようになりました。このZodの便利なところは、とにかくスキーマを細やかに指定できるというところであり、そのスキーマ自体が変数として取り扱うことが出来る点です。

例えばご当地バーガーショップのレシートデータを構造化しようと考えた場合、そのデータスキーマを設計する必要があります。注文された商品とその点数、テーブル番号、値段などがデータスキーマに含まれる必要があることでしょう。

Zodで表現するならば、だいたい以下のようになるかと思います。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import { z } from 'zod'

// 商品単品のスキーマ
const itemSchema = z.object({
// 商品名。便宜上30文字までとしてある
name: z.string().max(30),
// 商品価格。便宜上10万円までとしてある
price: z.number().min(0).max(100000),
})

// 注文のスキーマ
const orderSchema = z.object({
// 商品オブジェクト。どの商品を注文するのかを表す
item: itemSchema,
// 注文数。一度に同じ商品を20個まで注文できる
amounts: z.number().min(1).max(20),
})

// 担当者のスキーマ
const staffSchema = z.object({
// 担当者ID
id: z.number().min(1),
// 担当者名。便宜上30文字までとしてある
name: z.string().max(30),
})

// レシートのスキーマ
const receiptSchema = z.object({
// テーブル番号。1~45まである店舗ということにする
tableNumber: z.number().min(1).max(45),
// 注文の一覧
orders: z.array(orderSchema),
// 注文された日時の記録。デフォルトで現在時刻が記録される
orderedAt: z.date().default(new Date()),
// 注文受付担当者
staff: staffSchema,
})

こういう感じのスキーマ定義を、型がとても緩いPerlでも使えるようにしたら便利では?と思い、移植してみました。それが拙作のCPANライブラリであるPozです。

https://metacpan.org/pod/Poz

Pozのつかいかた

Pozをインストールする場合は cpanm などのPerlモジュール管理ツールをお使いいただくのが最も簡単です。

1
$ cpanm Poz

では、先ほどZodで表現してみたご当地バーガーショップのレシートデータ構造化スキーマを、Pozでもやってみます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
use strict;
use utf8;
use Poz qw/z/;
use Time::Piece ();

# new Date() の代わりを作っておく
my $new_date = sub {
Time::Piece::localtime->strftime('%Y-%m-%dT%H:%M:%SZ');
};

# 商品単品のスキーマ
my $item_schema = z->object({
# 商品名。便宜上30文字までとしてある
name => z->string->max(30),
# 商品価格。便宜上10万円までとしてある
price => z->number->min(0)->max(100000),
})->as('BurgerShop::Item');

# 注文のスキーマ
my $order_schema = z->object({
# 商品オブジェクト。どの商品を注文するのかを表す
item => $item_schema,
# 注文数。一度に同じ商品を20個まで注文できる
amounts => z->number->min(1)->max(20),
})->as('BurgerShop::Order');

# 担当者のスキーマ
my $staff_schema = z->object({
# 担当者ID
id => z->number->min(1),
# 担当者名。便宜上30文字までとしてある
name => z->string->max(30),
})->as('BurgerShop::Staff');

# レシートのスキーマ
my $receipt_schema = z->object({
# テーブル番号。1~45まである店舗ということにする
table_number => z->number->min(1)->max(45),
# 注文の一覧
orders => z->array($order_schema),
# 注文された日時の記録。デフォルトで現在時刻が記録される
ordered_at => z->datetime->default(sub { $new_date->() }),
# 注文受付担当者
staff => $staff_schema,
})->as('BurgerShop::Receipt');

なるべくインターフェースをZodに寄せてありますので、ほぼZodの使い方と一緒です。

違うとことがあるとすれば、 ->as('BurgerShop::Receipt') のあたりでしょうか。これは本家Zodにはない指定でして、Zodの場合は構造体の型を z.infer で生成することができるのですが、Perlの場合はHashRefに型を紐づけたい場合、blessするほかありません。

そのような言語の特性を吸収するべく、バリデーションが通ったHashRefについては as(...) で指定したクラスのオブジェクトとしてblessしてしまおう、という対応をとっています。

なお、実際にバリデーションを行うときには以下のようにします。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
my $data = {
table_number => 10,
orders => [
{
item => {
name => 'チーズバーガー',
price => 250,
},
amounts => 2,
},
{
item => {
name => 'ポテト',
price => 150,
},
amounts => 1,
},
],
staff => {
id => 1,
name => '山田太郎',
},
};

# データを検証
my ($result, $error) = $receipt_schema->safe_parse($data);

上記の例では safe_parse を使っていますが、本家Zodと同じように parse も使えます。ここら辺のインターフェースもZodによせてありますので、Zodに慣れている方であれば違和感なくご利用いただけるのではないでしょうか。

なお、この時以下のテストが通ります(実はこのエントリを書いていてバグに気が付いたので、急いで直して、v0.03をリリースしました・・・!!)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# エラーは無し
is($error, undef, 'expected no error');

# テーブル番号は10番
is($result->{table_number}, 10, 'table_number is 10');

# オーダー一覧はBurgerShop::Orderインスタンスの配列になっている
is_deeply($result->{orders}, [
bless({
item => {
name => 'チーズバーガー',
price => 250,
},
amounts => 2,
}, 'BurgerShop::Order'),
bless({
item => {
name => 'ポテト',
price => 150,
},
amounts => 1,
}, 'BurgerShop::Order'),
], 'orders is correct');

# スタッフもBurgerShop::Staffのインスタンスになっている
is_deeply($result->{staff}, bless({
id => 1,
name => '山田太郎',
}, 'BurgerShop::Staff'), 'staff is correct');

# 注文日時がデフォルト値(現在時刻)となっている
like($result->{ordered_at}, qr/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/, 'ordered_at is correct');

まあまあ複雑なデータ構造もいい感じに検証されており、無事に検証を通過したデータは適切にblessされているのがわかります。ちょっと頑張りましたね!

kuraとの連携

Perlの型ライブラリに kura というものがございまして、これはMetaCPANにある様々な型ライブラリのうち、 Data::Checks, Type::Tiny, Moose::Meta::TypeConstraint, Mouse::Meta::TypeConstraint, Specio などを横断的に使えるようにしてしまおうという、大変意欲的なものとなっています。

なんと、ありがたいことに、kuraの作者である こばけんさん からPozにコントリビュートをいただいており、 既にkuraとの連携 ができるように調整いただいております。本当にありがたいことです。

1
2
use Poz qw(z);
use kura Name => z->string->min(1)->max(255);

というか、Pozをリリースしてから2日以内くらい?でこれが出来るようになっていたんです。すごくないですか・・・?

まとめ

ZodをPerlに移植したようなバリデーター「Poz」を作った話をさせていただきました。

別の言語で使える便利そうなやつをPerlに持ってくるというのは、かなり楽しいです。

今のご時世、AIによるサポートを得ながらライブラリの開発ができますが、今回作ったPozもGitHub Copilotを存分に活用して開発しました。なかなか簡単に作れたので、ちょっとPerlに元気を与えたいと思った方にはぜひともチャレンジしてもらいたいなと思います。