黒曜 / @kokuyouwind
Leaner Technologies Inc. 所属
Railsエンジニア・SRE
普段は名古屋からフルリモート勤務
あなたはスーパーへのお使いを頼まれました。
「牛乳を1つ買って。卵があったら6つお願い」
スーパーには牛乳が10本、卵が10パックあります。
あなたが買うべき物はなんですか?
A: 牛乳1本
B: 牛乳6本
C: 牛乳1本、卵1パック
D: 牛乳1本、卵6パック
※ Gemini 3 Flashを使用、ランダム性有り
牛乳1本、卵1パック(10個入)
※ Gemini 3 Flashを使用、ランダム性有り
buy(Milk, 1)
if Egg.stock > 0
buy(Egg, 6)
end自然言語には曖昧性が入りやすい
主語や目的語の欠け、係り受けの間違いなど
解釈の余地が広い
プログラミング言語は意味論が厳密
評価できない文は構文エラー
解釈の余地はほぼ0
プロンプトを自然言語でなくプログラミング言語で書く
柔軟性は下がるが指示が厳密になるのでは?
ifのネスト
ループ
パターンマッチ
バックトラック
goroutine
関数呼び出し
制御構文・関数呼び出し
言語ごとの独特な機能
rbs-goose での実用例
カスタムコマンドのテストに関するTips
まとめ
制御構文・関数呼び出し
言語ごとの独特な機能
rbs-goose での実用例
カスタムコマンドのテストに関するTips
まとめ
Claude Codeのカスタムスラッシュコマンドとして
擬似コードプロンプトを実装し、挙動を確認
# Claude Code を起動
$ claude
> /plugin marketplace add kokuyouwind/claude-plugins
> /plugin install code-like-prompt@kokuyouwind-plugins
# 「牛乳を1つ買って。卵があったら6つお願い」
> /code-like-prompt:01-shopping-request { Milk.stock: 10, Egg.stock: 10 }
⏺ Bought 1 milk.
Bought 6 eggs.
# シェルから非対話モードで直接実行
$ claude -p "/code-like-prompt:01-shopping-request { Milk.stock: 10, Egg.stock: 10 }"
⏺ Bought 1 milk.
Bought 6 eggs.---
description: コード風プロンプト例1 牛乳ジョーク:妻の依頼
argument-hint: '{"Milk.stock": number, "Egg.stock": number}'
---
Emulate the following code internally (without using external tools or interpreter) with environment: $ARGUMENTS
Output only what puts() commands would output. Do not show any explanations, code, variables, or other messages.
```ruby
# Validate required arguments
raise "Required argument 'Milk.stock' is missing" if Milk.stock.nil?
raise "Required argument 'Egg.stock' is missing" if Egg.stock.nil?
# Shopping logic
puts("Bought 1 milk.")
if Egg.exists? # Egg.exists? means Egg.stock > 0
puts("Bought 6 eggs.")
end
```---
description: コード風プロンプト例1 牛乳ジョーク:妻の依頼
argument-hint: '{"Milk.stock": number, "Egg.stock": number}'
---
Emulate the following code internally (without using external tools or interpreter) with environment: $ARGUMENTS
Output only what puts() commands would output. Do not show any explanations, code, variables, or other messages.
```ruby
# Validate required arguments
raise "Required argument 'Milk.stock' is missing" if Milk.stock.nil?
raise "Required argument 'Egg.stock' is missing" if Egg.stock.nil?
# Shopping logic
puts("Bought 1 milk.")
if Egg.exists? # Egg.exists? means Egg.stock > 0
puts("Bought 6 eggs.")
end
```「外部ツールを使わずエミュレートする」
これを書かないとインタプリタで
コードを実行しようとする
---
description: コード風プロンプト例1 牛乳ジョーク:妻の依頼
argument-hint: '{"Milk.stock": number, "Egg.stock": number}'
---
Emulate the following code internally (without using external tools or interpreter) with environment: $ARGUMENTS
Output only what puts() commands would output. Do not show any explanations, code, variables, or other messages.
```ruby
# Validate required arguments
raise "Required argument 'Milk.stock' is missing" if Milk.stock.nil?
raise "Required argument 'Egg.stock' is missing" if Egg.stock.nil?
# Shopping logic
puts("Bought 1 milk.")
if Egg.exists? # Egg.exists? means Egg.stock > 0
puts("Bought 6 eggs.")
end
```ここが疑似コード指示の本体
(以降はこの部分のみ表示)
# 01-shopping-request.md
# 妻の依頼: 卵があったら(卵を)6個買って
puts("Bought 1 milk.")
if Egg.exists?
puts("Bought 6 eggs.")
end
# 01-shopping-misunderstanding.md
# 夫の理解: 卵があったら(牛乳を)6個買って
milk_amount = 1
if Egg.exists?
milk_amount = 6
end
puts("Bought #{milk_amount} milks.")# 「牛乳を1つ買って。卵があったら(卵を)6つお願い」
# 卵がない
> /code-like-prompt:01-shopping-request { Milk.stock: 10, Egg.stock: 0 }
⏺ Bought 1 milk.
# 卵がある
> /code-like-prompt:01-shopping-request { Milk.stock: 10, Egg.stock: 10 }
⏺ Bought 1 milk.
Bought 6 eggs.
# 「牛乳を1つ買って。卵があったら(牛乳を)6つお願い」
# 卵がない
> /code-like-prompt:01-shopping-misunderstanding { Milk.stock: 10, Egg.stock: 0 }
⏺ Bought 1 milk.
# 卵がある
> /code-like-prompt:01-shopping-misunderstanding { Milk.stock: 10, Egg.stock: 10 }
⏺ Bought 6 milks.if condition_a:
if condition_b:
print("foo")
else:
print("bar")期待する出力:
# a = b = true なので foo が出力されるべき
> /code-like-prompt:02a-dangling-else-outer-indent
{ condition_a: true, condition_b: true }
⏺ foo
# a = true, b = false なので 何も出力しないべき
> /code-like-prompt:02a-dangling-else-outer-indent
{ condition_a: true, condition_b: false }
⏺ bar
if (condition_a) {
if (condition_b) {
printf("foo");
}
} else {
printf("bar");
}C言語で書いて、中括弧でスコープを明示すればいけるかも?
# a = b = true なので foo が出力されるべき
> /code-like-prompt:02a-dangling-else-outer-block
{ condition_a: true, condition_b: true }
⏺ foo
# a = true, b = false なので 何も出力しないべき
> /code-like-prompt:02a-dangling-else-outer-block
{ condition_a: true, condition_b: false }
⏺ I need to trace through this code with the given values:
- condition_a = true
- condition_b = false
Following the execution:
1. condition_a is not NULL, so no error
2. condition_b is not NULL, so no error
3. condition_a is true, so enter the first if block
4. Inside that block, condition_b is false, so the inner if block is skipped
5. No else clause for the inner if, so nothing happens
6. The outer else clause is not executed because condition_a was true
Therefore, no printf() statements are executed, resulting in no output.Claude(Claude Code)は
「出力なし」を扱うのが苦手?
# a = b = true なので foo が出力されるべき
> /code-like-prompt:02a-dangling-else-outer-indent
{ condition_a: true, condition_b: true }
出力がない場合は()を出力
⏺ foo
# a = true, b = false なので 何も出力しない(代わりに () を出力する)べき
> /code-like-prompt:02a-dangling-else-outer-indent
{ condition_a: true, condition_b: false }
出力がない場合は()を出力
⏺ ()「出力がない場合は()を出力」という指示を追加
if level1:
if level2:
if level3:
print("foo")
else:
if level4:
print("bar")
else:
print("baz")
else:
if level3:
if level4:
print("qux")
else:
print("quux")
else:
if level2:
print("corge")
else:
if level3:
print("grault")
else:
print("garply")4段ネストの複雑なif
「出力がない場合は()を出力」という指示あり
else:
if level2:
print("corge")
else:
if level3:
print("grault")
else:
print("garply")(結果多いので貼らないけど)
すべての条件の組み合わせで意図通り出力!
Claude Codeは「何も出力しない」ことが苦手そう
「何も出力しない代わりに ~ を出力」という指示が有効
else の紐づくifが正しく解釈されないことがある
PythonのインデントよりCの中括弧のほうが得意そう
「出力なし」のようなケースを避ければ
elseの紐づけを間違えることはなさそう
深くネストしても問題なく動作する
count = 0
while count < max_count:
print("bar")
count += 1
print("baz")> /code-like-prompt:03b-while-counter { max_count: 3 }
⏺ bar
bar
bar
bazfor i in range(count):
print(f"foo{i}")> /code-like-prompt:03a-for-count { count: 5 }
⏺ foo0
foo1
foo2
foo3
foo4for i in range(outer_count):
for j in range(inner_count):
print(f"foo{i}{j}")> /code-like-prompt:03f-nested-loops { outer_count: 3, inner_count: 2 }
⏺ foo00
foo01
foo10
foo11
foo20
foo21items.each do |item|
puts item
end> /code-like-prompt:03c-each-collection { items: [hoge, fuga, piyo] }
⏺ hoge
fuga
piyoDir.glob(pattern).each do |file|
puts file
end「特定種類のファイルそれぞれに操作」を擬似コードで表現
(Dir.glob がどういう挙動をするかは未指定)
$ ls
test1.md test2.rb test3.c test4.md test5.rb
$ claude
# 外部ツールを許可しないと適当な出力を返す
/code-like-prompt:03h-filesystem-glob { pattern: *.md }
⏺ README.md
# search tools を許可すると正しく動作
> /code-like-prompt:03h-filesystem-glob { pattern: *.md }
allow search tools
⏺ I'll emulate this Ruby code with the given pattern *.md.
⏺ Search(pattern: "*.md", path: "/private/tmp/test")
⎿ Found 2 files (ctrl+o to expand)
⏺ test1.md
test4.mdfor, while, each などは意図通りに動作しやすい
ネストしたループも問題なく動作する
数字のカウントアップも適切
Dir.glob#each を内部のsearchツールで代替させるなど、
応用的な挙動も可能
ファイルや検索結果に対する操作を明示しやすそう
def calculate_score(base, bonus):
if bonus:
return base * 2
else:
return base
score = calculate_score(base, bonus)
if score >= 100:
print("Excellent")
elif score >= 50:
print("Good")
else:
print("Try again")# bonusなしで50点以上なので、 Good になるはず
> /code-like-prompt:06b-function-return { base: 80, bonus: false }
⏺ Good
# bonusありで 50 * 2 = 100 なので、 Excellent になるはず
> /code-like-prompt:06b-function-return { base: 50, bonus: true }
⏺ Excellent
# bonusにbooleanではなく数値を渡すと…?
> /code-like-prompt:06b-function-return { base: 50, bonus: 1 }
⏺ Excellent
> /code-like-prompt:06b-function-return { base: 50, bonus: 0 }
⏺ Good
# 0 がfalse, それ以外はtrue として扱われるっぽい問題なく解釈されそう
引数を渡す・返り値を返すという概念も問題ない
複雑な指示を分割する際に有用かも
引数の型が合わない場合もよしなに解釈される
おそらく書いた言語の仕様に沿ってシミュレートする
制御構文・関数呼び出し
言語ごとの独特な機能
rbs-goose での実用例
カスタムコマンドのテストに関するTips
まとめ
data Record = Record { label :: String, value :: Int }
deriving (Show, Eq)
main :: IO ()
main = do
-- record argument is provided as Record type
let record = getRecord "record"
case record of
Record "foo" v -> putStrLn $ "foo:" ++ show v
Record "bar" v | v > 10 -> putStrLn "bar-large"
Record "bar" v -> putStrLn "bar-small"
Record l _ -> putStrLn $ "other:" ++ l# `Record "foo" v -> putStrLn $ "foo:" ++ show v` に従って、 foo:5 が出て欲しい
> /code-like-prompt:04b-structural-match { record: { label: foo, value: 5 } }
⏺ foo:5
# `Record "bar" v | v > 10 -> putStrLn "bar-large"` に従って、bar-large が出て欲しい
> /code-like-prompt:04b-structural-match { record: { label: bar, value: 20 } }
⏺ bar-large
# `Record l _ -> putStrLn $ "other:" ++ l` に従って、 other:buzz が出て欲しい
> /code-like-prompt:04b-structural-match { record: { label: buzz, value: 5 } }
⏺ other:buzzdata Tree = Leaf String
| Branch Tree Tree
deriving (Show, Eq)
main :: IO ()
main = do
-- tree argument is provided as Tree type
let tree = getTree "tree"
case tree of
Leaf x -> putStrLn $ "leaf:" ++ x
Branch (Leaf "foo") (Leaf "bar") -> putStrLn "foo-bar"
Branch (Leaf "foo") _ -> putStrLn "foo-any"
Branch _ (Leaf "bar") -> putStrLn "any-bar"
Branch (Branch _ _) _ -> putStrLn "nested"
Branch _ _ -> putStrLn "other"# `Leaf x -> putStrLn $ "leaf:" ++ x` に従って、 leaf:foo が出て欲しい
> /code-like-prompt:04d-nested-match { tree: Leaf "foo" }
⏺ leaf:foo
# `Branch (Leaf "foo") _ -> putStrLn "foo-any"` に従って、 foo-any が出て欲しい
> /code-like-prompt:04d-nested-match
{ tree: Branch (Leaf "foo") (Leaf "piyo") }
⏺ foo-any
# `Branch (Branch _ _) _ -> putStrLn "nested"` に従って、 nested が出て欲しい
> /code-like-prompt:04d-nested-match
{ tree: Branch (Branch (Leaf "foo") (Leaf "bar")) (Leaf "bar"))
⏺ nested
かなり得意そう
そもそもLLMの原理が文字列処理なので、
「似た形を見つけて処理する」のが得意なのでは
「パターンを見つけてなにかする」プロンプトに使えそう
正規表現・sed・awkで書くような処理を
より柔軟に保守しやすく書けるかも
% 有向グラフの定義
% a -> b -> c -> d
% + - - - - ^
path(a, b).
path(b, c).
path(c, d).
path(a, c).
% パスを辿って接続しているかの定義
connected(X, Y) :- path(X, Y).
connected(X, Y) :- path(X, Z), connected(Z, Y).
% a から接続しているノードを探索(fail で強制バックトラックして全列挙)
?- connected(a, X), write(X), write(' '), fail.
> /code-like-prompt:07-b-multi-clause
⏺ b c d c dpath(a, b).
path(b, c).
path(c, d).
path(a, c).
connected(X, Y) :- path(X, Y).
connected(X, Y) :- path(X, Z), connected(Z, Y).
?- connected(a, X), write(X), write(' '), fail.
# b c d c d が出力されてほしいが、実行ごとに結果が変わってしまう
> /code-like-prompt:07-b-multi-clause
⏺ a b c c d
> /code-like-prompt:07-b-multi-clause
⏺ a b c d
コメントがあれば動くが、なしだと安定しない
コードコメントもプロンプトの一部なので挙動に影響
パターンマッチと違い、「同じ形の文字列」が少ないので
LLMが推測しにくかった?
func main() {
ch := make(chan string)
go func() {
ch <- "foo"
ch <- "bar"
}()
fmt.Println(<-ch)
fmt.Println(<-ch)
fmt.Println("baz")
}> /code-like-prompt:05b-channel-sync
⏺ foo
bar
bazfunc stage1(out chan<- string) {
out <- "foo"
out <- "bar"
close(out)
}
func stage2(in <-chan string, out chan<- string) {
for s := range in {
out <- s + "X"
}
close(out)
}
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go stage1(ch1)
go stage2(ch1, ch2)
for result := range ch2 {
fmt.Println(result)
}
fmt.Println("baz")
}> /code-like-prompt:05h-pipeline
⏺ fooX
barX
bazチャンネル経由での値受け渡しも基本的なものなら動作
関数と同じように捉えられていそう
当然ながらParallelな動作にはならない
Subagentとして立ち上げるとParallelにできるはず
Channelのやりとりが難しくて挫折
(05l-channel-messaging あたり)
制御構文・関数呼び出し
言語ごとの独特な機能
rbs-goose での実用例
カスタムコマンドのテストに関するTips
まとめ
Rubyの型定義を推論するClaude Code カスタムコマンド
型検査ツールのセットアップなども自動で実行する
# プラグインを追加
$ claude
> /plugin marketplace add kokuyouwind/claude-plugins
> /plugin install rbs-goose@kokuyouwind-plugins
# プロジェクト内の型を推論・ブラッシュアップ
> /rbs-goose:run# First Run Setup
unless File.exists?('./rbs_goose.yml')
follow_instruction(File.join(plugin_base_path, 'commands/setup.md'))
return
end
config = Config.load('./rbs_goose.yml')
case config.type_annotation_mode
when :inline
follow_instruction(File.join(plugin_base_path, 'internal/type_inline.md'))
when :file
follow_instruction(File.join(plugin_base_path, 'internal/type_file.md'))
end# Setup configurations
file = File.copy('templates/rbs_goose.template.yml', './rbs_goose.yml')
puts(file.contents)
until('If there are no problems with the above settings file, please answer yes. If you wish to update the file, please describe the changes.') do
file.update_contents
end
# Setup tools
config = Config.load('./rbs_goose.yml')
unless gemfile.contain?('rbs')
puts('setup rbs...')
fetch('https://github.com/soutaro/steep')
setup_rbs
# ...# Load Documentation
fetch('https://github.com/ruby/rbs/blob/master/docs/syntax.md')
while (typecheck_command.run.type_errors > 0) do
fix_type_errors
end
until (project.signatures.each { it.well_typed? }) do
refine_signatures
fix_type_errors
end制御構文・関数呼び出し
言語ごとの独特な機能
rbs-goose での実用例
カスタムコマンドのテストに関するTips
まとめ
いろんなパターンの引数でテストするのが大変なので、
goでテストを書いた
# kokuyouwind/claude-code/test
# すべてのテストを実行(めっちゃ時間がかかる)
$ go test -v ./code-like-prompt -timeout 0
# 特定のテストを実行
$ go test -v ./code-like-prompt -run Test01Shopping/shopping_request_with_eggs// Test01ShoppingRequest tests the 01-shopping-request command
func Test01ShoppingRequest(t *testing.T) {
tests := []TestCase{
{
Name: "shopping_request_with_eggs",
Command: "/code-like-prompt:01-shopping-request",
Args: map[string]interface{}{
"Milk.stock": 5,
"Egg.stock": 3,
},
ExpectedOutputs: []string{
"Bought 1 milk.",
"Bought 6 eggs.",
},
},
...
}
RunTestCases(t, tests)
}都度リクエストすると費用も時間もかさむため、
mitmproxyで同一プロンプトのリクエストをキャッシュ
(が、あまり安定していない)
# kokuyouwind/claude-code/test
# Record mode - always call API and update cache
BOOT_VCR_MODE=record go test -v
# Replay mode - only use cache (fail on cache miss)
BOOT_VCR_MODE=replay go test -v
# 標準実行で auto なので、自動で再生・追記される制御構文・関数呼び出し
言語ごとの独特な機能
rbs-goose での実用例
カスタムコマンドのテストに関するTips
まとめ
プロンプトを擬似コードで書くことが可能
自然言語より端的に書けて、厳密に動作する
厳密な制御構文と曖昧な指示を織り交ぜて記述できる
AIならではの不確実性はある
実際に擬似コードで書いた開発補助ツールがうまく動作した
エージェント内ツールを作るのに便利かも
興味があればいろいろ試してみてね!