0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Ninda 12日目: 型安全API 〜マクロで実現するコンパイル時型チェック〜

Last updated at Posted at 2025-12-11

今日のゴール

  • 型安全APIの利点を理解する
  • defineTuple マクロの使い方をマスターする
  • 型付きタプルでバグを防ぐ方法を学ぶ

動的型付けの問題

これまでの例では、タプルは動的に型付けされていました。

# 動的型付け: 実行時までエラーがわからない
let t = toTuple(strVal("user"), intVal(25), strVal("alice"))

# 間違ったアクセス(コンパイルは通る)
let age = t[1].strVal  # 実行時エラー: intValなのにstrValでアクセス

型安全APIを使うと、このようなエラーをコンパイル時に検出できます。


defineTupleマクロ

defineTuple マクロは、型付きタプルを定義します。

import ninda/macros/typed

# 型付きタプルの定義
defineTuple(User):
  name: string
  age: int64

# 使用例
let user = User(name: "alice", age: 25)
echo user.name  # "alice" (string型として保証)
echo user.age   # 25 (int64型として保証)

生成されるコード

defineTuple(User) は以下を自動生成します:

生成物 説明
type User オブジェクト型
toTuple(User) User → Tuple変換
fromTuple(User, Tuple) Tuple → User変換
UserPattern(...) 型安全なパターンビルダー

型付きタプルの使用

defineTuple(Task):
  taskType: string
  taskId: int64
  priority: int64

proc example() {.async.} =
  let ts = newSpaceManager().space()

  # 型付きオブジェクトを作成
  let task = Task(taskType: "process", taskId: 1001, priority: 5)

  # Tupleに変換して書き込み
  await ts.writeAsync(task.toTuple())

  # パターンで読み取り
  let result = await ts.readAsync(TaskPattern(taskType = some("process")))

  # 型付きオブジェクトに変換
  let readTask = Task.fromTuple(result)

  # 型安全なアクセス
  echo readTask.taskId    # int64として保証
  echo readTask.priority  # int64として保証

ポイント:

  • フィールド名でアクセス(インデックスではなく)
  • IDEの補完が効く
  • 型の不一致はコンパイルエラー

パターンビルダーの活用

*Pattern 関数で柔軟なパターンを作成:

defineTuple(Order):
  orderId: string
  customerId: int64
  status: string
  amount: float64

# 全オーダー(全フィールドワイルドカード)
let allPattern = OrderPattern()

# 特定顧客のオーダー
let customer100Pattern = OrderPattern(customerId = some(100'i64))

# pending状態のオーダー
let pendingPattern = OrderPattern(status = some("pending"))

# 複数条件の組み合わせ
let specificPattern = OrderPattern(
  customerId = some(100'i64),
  status = some("pending")
)

ポイント:

  • some(値) で具体値を指定
  • 指定しないフィールドはワイルドカード(任意の値にマッチ)

複数の型付きタプル

アプリケーションでは複数の型を定義します。

# ドメインモデルの定義
defineTuple(Product):
  sku: string
  name: string
  price: float64
  stock: int64

defineTuple(CartItem):
  cartId: string
  productSku: string
  quantity: int64

defineTuple(OrderEvent):
  eventType: string
  orderId: string
  timestamp: int64

各型は独立したスペースで管理するのがベストプラクティス:

let products = manager.space("products")
let carts = manager.space("carts")
let events = manager.space("events")

型安全APIの利点

観点 動的型付け 型安全API
エラー検出 実行時 コンパイル時
IDEサポート 限定的 完全(補完、型ヒント)
リファクタリング 危険 安全
ドキュメント コメント頼り 型が仕様

ベストプラクティス

1. 型定義をモジュールにまとめる

# types.nim
defineTuple(User):
  userId: string
  name: string
  role: string

defineTuple(Session):
  sessionId: string
  userId: string
  expiresAt: int64

2. パターンを定数化

const
  ALL_USERS = UserPattern()
  ADMIN_USERS = UserPattern(role = some("admin"))

3. ラッパー関数を提供

proc writeUser*(ts: TupleSpace, user: User) {.async.} =
  await ts.writeAsync(user.toTuple())

proc readUser*(ts: TupleSpace, userId: string): Future[Option[User]] {.async.} =
  let result = await ts.tryReadAsync(UserPattern(userId = some(userId)))
  if result.isSome:
    return some(User.fromTuple(result.get()))
  return none(User)

まとめ

学んだこと

トピック ポイント
defineTuple 型付きタプルを定義するマクロ
toTuple/fromTuple 型変換
パターンビルダー 型安全なパターン作成
利点 コンパイル時エラー検出、IDE支援

いつ使うか

場面 推奨
本番コード 型安全API
プロトタイプ 動的型付け(手軽さ優先)
外部データ連携 型安全API + バリデーション

演習問題

問題12-1: Eコマースモデル

以下のドメインモデルを型安全APIで定義してください:

  • Customer(customerId, name, email)
  • Product(productId, name, price, category)
  • Order(orderId, customerId, productId, quantity)
ヒント
  • 各モデルをdefineTupleで定義
  • フィールドの型: string(ID, name, email, category)、float64(price)、int64(quantity)
  • 各モデル用のスペースを作成: customers, products, orders

問題12-2: パターン検索

問題12-1のモデルを使って、以下の検索を実装してください:

  • 特定カテゴリの商品一覧
  • 特定顧客の注文履歴
  • 特定商品の注文一覧
ヒント
  • ProductPattern(category = some("electronics")) のようにパターンを作成
  • readAllAsyncで複数件取得
  • 結果はseq[Tuple]なので、Product.fromTupleで変換

問題12-3: 安全な型変換

外部から受け取ったJSONを型安全に変換する関数を実装してください。変換に失敗した場合はOption[T]でnoneを返すようにしてください。

ヒント
  • try-exceptでエラーをキャッチ
  • 要素数が違う、型が違うなどのケースに対応
  • safeParse[T](tup: Tuple): Option[T] のようなジェネリック関数を作成
  • 失敗時はnone(T)、成功時はsome(T.fromTuple(tup))

前回: バルク操作 | 目次 | 次回: Producer-Consumer

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?