【Redis / Nginx】Programming in Lua (first edition) Part I のメモ

この記事は Redis Advent Calendar 2016, 18日目の記事です。

Programming in Lua (first edition) を読んで Luaに入門してみたのでメモを残しておきます。きっかけは Redis の Lua Scripting をやる機会ができたからです。
Part I から IV までありますが長いので 今回は Part I の印象に残ったところを書きます。
ちなみに First Edition は Lua 5.0 を前提に書かれています。 現在 (2016/12) の Lua 5.3 をサポートした Fourth Edition は Amazon などで買えるようです。
Lua 5.3 のリファレンスマニュアルは日本語訳もあるのでこちらも参考になりそうです。

Part I · The Language

Lua は 一般には組み込み用言語として使われ, NginxRedis などミドルウェアの拡張にも用いられる。

1 – Getting Started

環境は macOS Sierra。 brew で Lua をインストールする。

$ brew update
$ brew install lua
$ lua -v
Lua 5.2.4  Copyright (C) 1994-2015 Lua.org, PUC-Rio

Luaの代表的なモジュールマネージャに luarocks がある。 試しに table型を出力するための inspect をインストールする。

$ luarocks install inspect

lua コマンドで他の軽量言語のように REPL 環境に入れる。-e option で 直接的に実行できる。

$ lua -e "print(math.sin(12))"
-0.53657291800043

コメントが double hyphen ( — ) なのがちょっと戸惑う。
Luaでは global variable は最初に値が割り当てられるまでは デフォルトで nil が入る。また変数に nil を割り当てることもできる。

2 – Types and Values

Luaに number型 (double-precision floating-point) はあるが integer型はない。boolean型は true / false。
Lua では関数がファーストクラス (first-class values) なので変数に保持でき別の関数に値として渡せたり, 関数の返り値にもできる。

print(type("Hello world"))  --> string
print(type(10.4*3))         --> number
print(type(print))          --> function
print(type(type))           --> function
print(type(true))           --> boolean
print(type(nil))            --> nil
print(type(type(X)))        --> string

Lua ランタイムは numbers と strings 間で自動変換を行う。

print("10" + 1)           --> 11
print("10 + 1")           --> 10 + 1

Lua で特徴的なのが連想配列のような Table型。

polyline = {color="blue", thickness=2, npoints=4,
    {x=0,   y=0},
    {x=-10, y=0},
    {x=-10, y=1},
    {x=0,   y=1}
}

print(polyline["color"]) --> blue
print(polyline[2].x)    --> -10

3 – Expressions

算術演算子は普通な印象。
論理演算子は and/or/not で false と nil は false, その他は true と評価される。注意が必要なのは and で false の場合は最初の引数が返る。

print(4 and 5)         --> 5
print(nil and 13)      --> nil
print(false and 13)    --> false
print(4 or 5)          --> 4
print(false or 5)      --> 5

print(not nil)      --> true
print(not false)    --> true
print(not 0)        --> false
print(not not nil)  --> false

関係演算子には < > <= >= == ~= があり, 全て true か false のどちらかを返す。

a = {}; a.x = 1; a.y = 0
c = a
print(c == a)   --> true
print(c ~= a)   --> false

文字列連結は .. (two dots) でこれはもう慣れるしかない。

print("Hello " .. "World")  --> Hello World
print(0 .. 1)               --> 01

table への値の代入や参照も自由度が高い。

a = {}     -- create a table and store its reference in `a'
a.x = 10        -- same as a["x"] = 10
print(a.x)      -- same as print(a["x"])
print(a.y)      -- same as print(a["y"])

4 – Statements

local をつける場合は local variable になるが他は global variable となる。block は do-end で区切る。

変数への値の割り当て。

x = 10
y = 20
a = {i=10, j=20}

x, y = y, x  -- swap `x' for `y'
a["i"], a["j"] = a["j"], a["i"] -- swap `a["i"]' for `a["j"]'

print(x, y, a["i"], a["j"]) --> 20	10	20	10

制御構造は if-then-else, while-do, for-do, repeat-until がある。

以下は table を ipairs() で制御変数 index を追加してアクセスする例。

inspect = require("inspect")

days = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}
revDays = {}

for i,v in ipairs(days) do
    revDays[v] = i
end

print(inspect(revDays))
--[[
{
  Friday = 6,
  Monday = 2,
  Saturday = 7,
  Sunday = 1,
  Thursday = 5,
  Tuesday = 3,
  Wednesday = 4
}
--]]

5 – Functions

関数の引数が足りない場合は nil が入り, 多い場合は破棄される。この辺はだいぶ緩い。

function f(a, b) print(a, b) return a or b end

f(3) --> 3	nil
f(3,4) --> 3    4
f(3,4,5) --> 3  4

関数はファーストクラスなので値に入れたりできる。また, 既存の関数の参照先を変えることもできてしまう。

a = {p = print}
a.p("Hello World") --> Hello World
print = math.sin  -- `print' now refers to the sine function
a.p(print(1))     --> 0.841470
sin = a.p         -- `sin' now refers to the print function
sin(10, 20)       --> 10      20

6 – More about Functions

table library の sort関数は重宝しそう。

network = {
    {name = "grauna",  IP = "210.26.30.34"},
    {name = "arraial", IP = "210.26.30.23"},
    {name = "lua",     IP = "210.26.23.12"},
    {name = "derain",  IP = "210.26.23.20"},
}

table.sort(network, function (a,b)
    return (a.name > b.name)
end)

print(inspect(network))
--[[
{ {
    IP = "210.26.23.12",
    name = "lua"
  }, {
    IP = "210.26.30.34",
    name = "grauna"
  }, {
    IP = "210.26.23.20",
    name = "derain"
  }, {
    IP = "210.26.30.23",
    name = "arraial"
  } }
--]]

匿名関数の例。

function newCounter ()
    local i = 0
    return function ()   -- anonymous function
        i = i + 1
        return i
    end
end

c1 = newCounter()
print(c1())  --> 1
print(c1())  --> 2

7 – Iterators and the Generic for

generic for を使うと簡単にイテレーションを設計できる。構文は for {var} in {list} do {body} end で割と Python に近い。

function list_iter (t)
    local i = 0
    local n = #t
    return function ()
            i = i + 1
            if i <= n then return t[i] end
           end
end

-- generic for
t = {10, 20, 30}
for element in list_iter(t) do
    print(element)
end

table.getn(t) は Lua 5.1 で deprecated になり 5.2 で削除されたが代わりに #t が使えるようになった。

8 – Compilation, Execution, and Errors

Lua library の Load は `require` で行う。環境変数 LUA_PATH が最初にチェックされる仕組み。
また, Lua は C packages (shared library) を簡単に呼べるのも特徴のひとつ。

`assert` を使ったエラーの送出。

print "enter a number:"
n = assert(io.read("*number"), "invalid input")

引数の型チェックを行い期待する型でない場合にエラーを発生させる例。

function foo (str)
    if type(str) ~= "string" then
        error("string expected")
    end
    print(str)
end

foo("abc")
foo({x=1}) --> Error

9 – Coroutines

Coroutine は Lua の大きな特徴のひとつで並行性を提供する。non-preemptive なマルチスレッドの一種である。
Coroutineはタスクの切り替えコストが小さく, 関数の呼び出しコストと同等のレベルであるらしい。

coroutine.create で 生成する。yield で処理を一時停止しておき後で再開できる。

co = coroutine.create(function ()
        for i=1,10 do
            print("co", i)
            coroutine.yield()
        end
    end)

coroutine.resume(co)    --> co   1

print(coroutine.status(co))   --> suspended

coroutine.resume(co)
coroutine.resume(co)
coroutine.resume(co)
coroutine.resume(co)
coroutine.resume(co)
coroutine.resume(co)
coroutine.resume(co)
coroutine.resume(co)
coroutine.resume(co)
coroutine.resume(co) --> 10

print(coroutine.resume(co)) --> false	cannot resume dead coroutine

coroutineを使った producer-consumer pattern の例 も紹介されている。

10 – Complete Examples

Part I (introduction) の最後として, データ記述言語として Lua を使う方法や マルコフ連鎖 の実装例を紹介している。


[1] LuaTutorial
[2] Chapter 11: Scripting Redis with Lua
[3] LuaSocket
[4] Programming言語Lua紹介(Internet版)
[5]
RedisのLuaスクリプティング機能について
[6] redis で Lua を使ってみたので備忘録
[7] More Transactional Redis (2) - Lua Scripting in Action