今日のゴール
- パターンマッチングの仕組みを深く理解する
- 効率的なパターン設計を学ぶ
- 第1フィールドインデックスの活用法を知る
パターンとは何か
タプルスペースの真の力は、パターンマッチングにあります。
従来のデータアクセス:
data[42] → 特定のアドレスで取得
タプルスペース:
("task", ?) → 「taskに関する何か」という条件で検索
アドレスではなく「形」で検索できることが、タプルスペースの革新的な点です。
ワイルドカードの使い方
nilValue() がワイルドカードを表します。
# 完全マッチ: 両方の値が一致するタプルのみ
let exact = toPattern(strVal("task"), intVal(42))
# 部分マッチ: "task"で始まる任意のタプル
let partial = toPattern(strVal("task"), nilValue())
# 全ワイルドカード: 2要素の任意のタプル
let any2 = toPattern(nilValue(), nilValue())
マッチングのルール
3つの基本ルール
| ルール | 説明 |
|---|---|
| 長さの一致 | パターンとタプルの要素数は同じ |
| 型の一致 | ワイルドカード以外は型と値が一致 |
| ワイルドカード |
nilValue()は何でもマッチ |
図解
コード例
# 型が違うと別のタプル
await ts.writeAsync(toTuple(strVal("num"), intVal(42))) # 整数の42
await ts.writeAsync(toTuple(strVal("num"), strVal("42"))) # 文字列の"42"
# 整数の42だけを検索
let intPattern = toPattern(strVal("num"), intVal(42))
let result = await ts.tryReadAsync(intPattern) # 整数版のみマッチ
第1フィールドインデックス
nindaは第1フィールドでインデックスを持っています。これが検索効率の鍵です。
効率的なパターン vs 非効率なパターン
| パターン | 効率 | 理由 |
|---|---|---|
("task", *) |
✅ O(1) | 第1フィールドでインデックス参照 |
(*, 42) |
❌ O(n) | 全タプルをスキャン |
(*, *, "target") |
❌ O(n) | 全タプルをスキャン |
タイプタグパターン
第1フィールドを「タイプタグ」として使うのがベストプラクティスです。
# ✅ 良い設計: タイプタグが第1フィールド
("order", 1001, "pending")
("order", 1002, "shipped")
("inventory", "SKU-001", 50)
("user", "alice", "admin")
# ❌ 避けるべき設計: タイプタグが第2フィールド
(1001, "order", "pending") # 検索が非効率
利点
- 高速検索: インデックスが効く
- 整理されたスペース: 種類ごとにグループ化
- 可読性: タプルの意味が明確
パターン設計のガイドライン
1. 第1フィールドをタイプタグに
("log", level, component, message) # ✅
(timestamp, "log", level, message) # ❌
2. 頻繁に検索する値を前方に
# levelで頻繁に検索するなら
("log", level, component, message)
# componentで頻繁に検索するなら
("log", component, level, message)
3. 適切な粒度を選ぶ
# ❌ 細かすぎ
("user", "field", "name", "alice")
("user", "field", "age", 25)
# ✅ 適切
("user", "alice", 25, "admin")
実践例: ログフィルタリング
# ログを書き込み
await ts.writeAsync(toTuple(strVal("log"), strVal("error"), strVal("auth"), strVal("Invalid token")))
await ts.writeAsync(toTuple(strVal("log"), strVal("info"), strVal("server"), strVal("Started")))
await ts.writeAsync(toTuple(strVal("log"), strVal("error"), strVal("db"), strVal("Connection lost")))
# エラーログだけを取得
let errors = await ts.readAllAsync(
toPattern(strVal("log"), strVal("error"), nilValue(), nilValue())
)
# → 2件のエラーログ
# authコンポーネントのログだけを取得
let authLogs = await ts.readAllAsync(
toPattern(strVal("log"), nilValue(), strVal("auth"), nilValue())
)
# → 1件のauthログ
まとめ
学んだこと
| トピック | ポイント |
|---|---|
| ワイルドカード |
nilValue() は何でもマッチ |
| マッチングルール | 長さ一致、型一致 |
| インデックス | 第1フィールドが鍵 |
| タイプタグ | 第1フィールドを識別子として使う |
設計の原則
第1フィールド = タイプタグ(効率的な検索の鍵)
第2フィールド以降 = よく検索する値を前方に
演習問題
問題5-1: 商品検索システム
商品タプル ("product", name, price, category) を10個作成し、以下を実装してください:
- 特定カテゴリの商品一覧取得
- 特定価格の商品検索
ヒント
- カテゴリ検索:
toPattern(strVal("product"), nilValue(), nilValue(), strVal("electronics")) - 価格検索:
toPattern(strVal("product"), nilValue(), intVal(1000), nilValue()) - 「1000円以下」のような範囲検索はパターンマッチングだけでは実現できません。
readAllで取得後にフィルタリングが必要です。
問題5-2: イベントフィルタ
イベントタプル ("event", type, source, timestamp) を作成し、typeとsourceの両方で絞り込む検索を実装してください。
ヒント
- 両方指定:
toPattern(strVal("event"), strVal("click"), strVal("button"), nilValue()) - パターンマッチングでは、指定した位置の値が完全一致するタプルのみがマッチします。
問題5-3: 効率比較
以下のパターンを使って1万件のタプルから検索し、処理時間を比較してください:
- 第1フィールドが具体値のパターン
- 第1フィールドがワイルドカードのパターン
ヒント
-
cpuTime()で計測:let start = cpuTime(); ...; echo cpuTime() - start - 差が出にくい場合は、検索を1000回ループして合計時間を比較
前回: 基本操作 | 目次 | 次回: 非同期プログラミング