📜

Arweave AOにおけるLuaの基本記法まとめ

に公開

Arweave AO(Actor Oriented)では、プロセスの記述にLuaプログラミング言語を使用します。
この記事では、AOでLuaを書く際の基本的な記法と、AO特有のパターンについて解説します。

変数とデータ型

基本的な変数宣言

-- グローバル変数
MyVariable = "Hello World"
Counter = 42
IsActive = true

-- ローカル変数
local localVar = "ローカル変数"
local number = 123

データ型

Luaは動的型付け言語で、以下のデータ型をサポートしています。

-- 数値型
local integer = 42
local float = 3.14
local scientific = 1.23e-4

-- 文字列型
local str1 = "ダブルクォート"
local str2 = 'シングルクォート'
local multiline = [[
複数行の
文字列も可能
]]

-- 真偽値
local isTrue = true
local isFalse = false

-- nil(未定義値)
local nothing = nil

テーブル(配列・辞書)

Luaのテーブルは配列と辞書の両方の機能を持つ柔軟なデータ構造です。

配列

-- 配列(インデックスは1から開始)
local fruits = {"apple", "banana", "orange"}
print(fruits[1])  -- "apple"
print(#fruits)    -- 3(配列の長さ)

辞書(連想配列)

-- 辞書
local person = {
    name = "太郎",
    age = 25,
    active = true
}

-- アクセス方法
print(person.name)     -- "太郎"
print(person["age"])   -- 25

複合テーブル

-- 配列と辞書の混合
local mixed = {
    "first",           -- [1]
    "second",          -- [2]
    name = "Mixed",    -- ["name"]
    count = 100        -- ["count"]
}

-- ネストしたテーブル
local config = {
    database = {
        host = "localhost",
        port = 5432,
        credentials = {
            username = "admin",
            password = "secret"
        }
    }
}

関数

基本的な関数定義

-- 通常の関数
function greet(name)
    return "Hello, " .. name
end

-- ローカル関数
local function add(a, b)
    return a + b
end

-- 無名関数
local multiply = function(x, y)
    return x * y
end

高度な関数機能

-- 複数戻り値
function getCoordinates()
    return 10, 20
end
local x, y = getCoordinates()

-- 可変長引数
function sum(...)
    local args = {...}
    local total = 0
    for i = 1, #args do
        total = total + args[i]
    end
    return total
end

-- 使用例
local result = sum(1, 2, 3, 4, 5)  -- 15

制御構文

条件分岐

local score = 85

if score >= 90 then
    print("優秀")
elseif score >= 70 then
    print("良好")
else
    print("要改善")
end

ループ処理

-- 数値ループ
for i = 1, 10 do
    print("Count: " .. i)
end

-- 逆順ループ
for i = 10, 1, -1 do
    print(i)
end

-- 配列の反復
local items = {"a", "b", "c"}
for i = 1, #items do
    print(i, items[i])
end

-- キー・値ペアの反復(全要素)
local data = {name = "John", age = 30, city = "Tokyo"}
for key, value in pairs(data) do
    print(key, value)
end

-- 配列専用の反復(連続インデックスのみ)
for index, value in ipairs(items) do
    print(index, value)
end

-- while文
local count = 0
while count < 5 do
    print("Count: " .. count)
    count = count + 1
end

AO特有の記法

AOでは、通常のLuaに加えていくつかの特別な規則があります。

永続化される変数

-- 大文字で始まるグローバル変数は自動的に永続化される
PersistentData = PersistentData or {}
TotalSupply = 1000000
Balances = Balances or {}

-- 条件初期化パターン(プロセス再起動対応)
if not Owner then
    Owner = ao.env.Process.Owner
end

AO組み込み関数

-- メッセージ送信
ao.send({
    Target = "プロセスID",
    Tags = {
        Action = "Transfer",
        Amount = "100"
    },
    Data = "追加データ"
})

-- 環境変数へのアクセス
local processId = ao.id
local owner = ao.env.Process.Owner

メッセージハンドラー

AOの特徴であるメッセージ処理システムです。

基本的なハンドラー

Handlers.add(
    "balance_check",  -- ハンドラー名
    Handlers.utils.hasMatchingTag("Action", "Balance"),  -- 条件
    function(msg)     -- 処理関数
        local account = msg.Tags.Account or msg.From
        local balance = Balances[account] or 0
        
        ao.send({
            Target = msg.From,
            Tags = {
                Action = "Balance-Response",
                Balance = tostring(balance)
            }
        })
    end
)

複雑な条件のハンドラー

-- カスタム条件関数
local function isValidTransfer(msg)
    return msg.Tags.Action == "Transfer" and
           msg.Tags.To and
           msg.Tags.Amount and
           tonumber(msg.Tags.Amount) > 0
end

Handlers.add(
    "transfer",
    isValidTransfer,
    function(msg)
        local from = msg.From
        local to = msg.Tags.To
        local amount = tonumber(msg.Tags.Amount)
        
        -- 転送処理
        if Balances[from] and Balances[from] >= amount then
            Balances[from] = Balances[from] - amount
            Balances[to] = (Balances[to] or 0) + amount
            
            ao.send({
                Target = msg.From,
                Tags = { Action = "Transfer-Success" }
            })
        else
            ao.send({
                Target = msg.From,
                Tags = { Action = "Transfer-Error", Error = "Insufficient balance" }
            })
        end
    end
)

よく使うパターン

安全な型変換

-- 安全な数値変換
local function safeToNumber(value, default)
    local num = tonumber(value)
    return num and num or (default or 0)
end

-- 安全な文字列変換
local function safeToString(value)
    return tostring(value or "")
end

バリデーション関数

local function validateTransfer(from, to, amount)
    -- 送信者・受信者チェック
    if not from or not to or from == to then
        return false, "Invalid sender or recipient"
    end
    
    -- 金額チェック
    local numAmount = tonumber(amount)
    if not numAmount or numAmount <= 0 then
        return false, "Invalid amount"
    end
    
    -- 残高チェック
    if not Balances[from] or Balances[from] < numAmount then
        return false, "Insufficient balance"
    end
    
    return true, numAmount
end

-- 使用例
local isValid, amountOrError = validateTransfer(msg.From, msg.Tags.To, msg.Tags.Amount)
if not isValid then
    ao.send({
        Target = msg.From,
        Tags = { Action = "Error", Message = amountOrError }
    })
    return
end

ユーティリティ関数

-- テーブルのコピー
local function deepCopy(original)
    local copy = {}
    for key, value in pairs(original) do
        if type(value) == "table" then
            copy[key] = deepCopy(value)
        else
            copy[key] = value
        end
    end
    return copy
end

-- テーブルが空かチェック
local function isEmpty(tbl)
    return next(tbl) == nil
end

-- 配列の要素を検索
local function findInArray(array, value)
    for i, v in ipairs(array) do
        if v == value then
            return i
        end
    end
    return nil
end

エラーハンドリング

pcall(Protected Call)

-- 安全な関数実行
local success, result = pcall(function()
    return someRiskyFunction()
end)

if success then
    print("成功: " .. tostring(result))
else
    print("エラー: " .. tostring(result))
end

assert

-- 条件チェック
local function divide(a, b)
    assert(type(a) == "number", "第1引数は数値である必要があります")
    assert(type(b) == "number", "第2引数は数値である必要があります")
    assert(b ~= 0, "ゼロで割ることはできません")
    return a / b
end

カスタムエラーハンドリング

local function safeExecute(func, errorHandler)
    local success, result = pcall(func)
    if success then
        return result
    else
        if errorHandler then
            return errorHandler(result)
        else
            print("エラーが発生しました: " .. tostring(result))
            return nil
        end
    end
end

-- 使用例
local result = safeExecute(
    function() return calculateSomething() end,
    function(error) return "計算に失敗しました: " .. error end
)

実践的な例:簡単なトークンコントラクト

以下は、これまでの知識を組み合わせた実践的な例です。

-- トークンの基本情報
TokenName = "MyToken"
TokenSymbol = "MTK"
TotalSupply = 1000000
Balances = Balances or {}

-- 初期化
if not Owner then
    Owner = ao.env.Process.Owner
    Balances[Owner] = TotalSupply
end

-- ユーティリティ関数
local function hasBalance(account, amount)
    return Balances[account] and Balances[account] >= amount
end

-- 残高照会ハンドラー
Handlers.add(
    "balance",
    Handlers.utils.hasMatchingTag("Action", "Balance"),
    function(msg)
        local account = msg.Tags.Account or msg.From
        local balance = Balances[account] or 0
        
        ao.send({
            Target = msg.From,
            Tags = {
                Action = "Balance-Response",
                Account = account,
                Balance = tostring(balance)
            }
        })
    end
)

-- 転送ハンドラー
Handlers.add(
    "transfer",
    Handlers.utils.hasMatchingTag("Action", "Transfer"),
    function(msg)
        local from = msg.From
        local to = msg.Tags.To
        local amount = tonumber(msg.Tags.Amount)
        
        -- バリデーション
        if not to or not amount or amount <= 0 then
            ao.send({
                Target = from,
                Tags = { Action = "Transfer-Error", Error = "Invalid parameters" }
            })
            return
        end
        
        if not hasBalance(from, amount) then
            ao.send({
                Target = from,
                Tags = { Action = "Transfer-Error", Error = "Insufficient balance" }
            })
            return
        end
        
        -- 転送実行
        Balances[from] = Balances[from] - amount
        Balances[to] = (Balances[to] or 0) + amount
        
        ao.send({
            Target = from,
            Tags = {
                Action = "Transfer-Success",
                From = from,
                To = to,
                Amount = tostring(amount)
            }
        })
    end
)

まとめ

AOでのLuaプログラミングは、通常のLuaの知識に加えて以下の点を理解することが重要です。

  1. 永続化の概念: 大文字で始まるグローバル変数の自動永続化
  2. メッセージドリブンアーキテクチャ: Handlersを使った非同期処理
  3. 状態管理: プロセスの再起動に対応した条件初期化
  4. エラーハンドリング: 分散環境での堅牢なエラー処理

これらの概念を理解することで、AOプラットフォーム上で効率的で安全なプロセスを構築できるようになります。
AOの分散コンピューティング環境では、従来の同期的なプログラミングとは異なる非同期・メッセージベースの思考が求められますが、Luaのシンプルな記法がその複雑さを軽減してくれます。

Arweave Japan

Discussion