ホーム‎ > ‎Lua 入門‎ > ‎

Lua 入門:最初の例〜関数

本ページを読む前に,Lua 入門 を読んで Lua のインストールを済ませてください.

最初の例

まずはやはり Hello, World でしょう.次の内容を hello.lua として保存してください:

print('Hello, Lua world!')

その後,

$ lua hello.lua 

と実行すると,

Hello, Lua world!

と表示されます.print( ... ) は,括弧の中身に書かれたものを画面(標準出力)に表示します.
シングルクオートで囲まれた ' ... ' は文字列を表します(ダブルクオートを使って "Hello, Lua world!" としても同じ事です).

これだけではつまらないので,計算をさせてみましょう.以下の例は全て,.lua で終わるスクリプトに記述し,lua コマンドで実行させるようにしてください.
次は x = cos(x) の解を二分法によって 10-10 の精度で求めるものです.字下げは単に見やすくするためのものです.

b, e = 0, 1 -- init
local cos = math.cos
while (e-b)>1e-10 do
  x = (b+e)/2
  f = (x-cos(x)>0)
  b, e = (f and b or x), (f and x or e)
end
x = (b+e)/2
print (x, cos(x))

Lua でよく使われる表現がいくつか出てきていますので,順番に解説していきます.
  • 1 行目の b, e = 0, 1 は,変数 b と変数 e にそれぞれ 0, 1 を同時に代入しています.
    「同時に」というのがミソで,例えば a, b = b, a と書くとこれは正しく変数 a と変数 b の中身を交換してくれます
    このプログラムでは,「b と e の間に x = cos(x) の解がある」ということを示す変数として使っています.

  • -- から行末まではコメントです.

  • 2 行目で,ローカル変数 cos に関数 math.cos() (三角関数 cos,弧度法単位)を代入しています.
    一見意味が無いように見えますが,このように「あとで使うものをどんどんローカル変数に放り込んでしまう」ことは,高速化のために Lua でよく行われることです.

  • 3 行目から始まる while 条件式 do ... end は条件式が真の間 ... を繰り返すループです.
    ここでは区間幅 e-b が 10-10 より大きい場合に,内部の「区間を半分に分割する」処理をしています.

  • 4 行目は変数 x に区間 [b,e] の中点 (b+e)/2 の計算結果を代入しています.「変数 x の値と (b+e)/2 の値が等しい」という条件ではありません.

  • 5 行目は変数 f に x-cos(x)>0 という命題の真理値(truefalse)を代入しています.
    なお,値が等しいことを示す論理演算子は == で,異なることは ~= を使って書きます.

  • 6 行目では,変数 b と変数 e にそれぞれ f and b or xf and x or e を同時に代入しています.
    ここで,f and b or x ( = (f and b) or x ) は Lua 独特の言い回しで,おおざっぱにいって C 言語でいう f?b:xと同じものとなります.
    言い換えれば,f が true(かつ b は false でも nil でもない)ならば f and b or x の値は b に,f が false ならば f and b or x の値は x になります.

    f が true(つまり x > cos(x))なら,方程式の解は [b,x] の間にあり,そうでなければ解は [x,e] の間にあるので,これによって区間 [b,e] を更新しています.

  • 8 行目の時点で,解の存在範囲 [b,e] の幅が 10-10 以下となったので,その中点 (b+e)/2 をもう一度 x に代入.

  • 9 行目で,最終結果の x, cos(x) の値を出力しています.私の環境では

    0.73908513321658 0.73908513321421

    となりました.
Lua で扱われるデータには,内部では以下のように型がついています.type(x)値 x の型を調べることができます.
  • nil:変数の値が初期化されていない場合はこの値を持つものとして扱われます.
    変数に nil を代入することも可能で,nil をうまく使うことでプログラムが簡単になることもあります.
  • ブール値(true か false)
  • 数値(倍精度浮動小数点数)
  • 文字列
  • 関数
  • テーブル:連想配列にあたるもので,Lua においては唯一のデータ構造です.
  • ユーザーデータ:Lua が組み込まれた C 言語のプログラム側で扱われる型です.例えば,LuaTeX におけるノードなどがこれにあたります.
  • スレッド(説明省略)
後半の 4 つは,変数に格納されるのは実体への参照のみです(後でもう一回見ていきます).
Lua のデータには型がありますが,
変数には型はありません.数値の代入されていた変数に,関数を代入することも可能です.

制御構造

以下の例を用いて説明します.Lua 単独では多バイト文字には対応しておらず,文字列は単なるバイト列としての扱いです(Debian 7 は文字コードが UTF-8 のため,それでもそんなに問題はおきないここでは説明とネタのために文字列やコメントに日本語を用いていますが,プログラム中に日本語を書くことはできれば避けてください.

print ('6 * 9 = ')
n = tonumber(io.read())
while not n do
  print ('入力したものは数ではありません.やり直し: ')
  n = tonumber(io.read())
end

if n==54 then
  print('正解')
elseif n==42 then
  print('13進法ではそうなりますが…….')
else
  print('はずれ')
end

io.read() は,1 行分の文字列をキーボード(標準入力)から読み込みます.2 行目などでは,それを tonumber() で数値に変換しています.

while ループ

3--6 行目が while ループです.while と do の間の not n という条件が true の間,中の 4, 5 行目の内容を実行します.

tonumber() は数値として認識できる文字列以外は全部 nil に変換します.また,not n は n が nilfalse の場合に限り true となります.よって,

(not n が true)iff(n が nil か false)iff(入力は数値として認識されない)

であるので,上の while ループは数値が入力されるまで再入力させていることになります.

条件判断

後半では,n の値によって処理を分けています.
  • n==54 であった場合は,最初の then 以降の print('正解') を実行します.
  • n==54 でなく,かつ n==42 であったときは,2 つめの then 以降の print('13進法...') を実行します.
  • n==54 でも n==42 でもなかったときは, else 以後の print('はずれ') を実行します.
elseif ... then ... や,else ... は不要なら省くことができるので,最小の形は

if 条件式 then ... end

という形です. elseif … then は何個でも書くことができます.

for ループ

次のプログラムは,Gauß--Legendre の算術幾何平均による公式(Wikipedia の記事)で円周率を求めるものです:

local sqrt = math.sqrt
a, b, t,p  = 1, 1/sqrt(2), 1/4, 1
for i = 1,4 do
  a, b, t, p = (a+b)*0.5, sqrt(a*b), t-p*(a-b)^2/4, 2*p
  print ('step ' .. i .. ': ' ..  (a+b)^2/(4*t) )
end

4, 5 行目の部分は,i が 1 から 4 まで 1 ずつ増加しながら計 4 回実行されます.出力結果は次のようになります:

step 1: 3.1405792505222
step 2: 3.1415926462135
step 3: 3.1415926535898
step 4: 3.1415926535898

ちなみに,5 行目の .. は文字列を連結する演算子です(数値は自動的に文字列に変換されます).

一般に,このような数値 for 文は

for 変数 = 初期値,終了値,増分値 do
  処理内容
end

という形になります.例えば, for i = 10, 1, -1 do ... end だと,i は 10 から 1 まで 1 ずつ下がっていきます.上の例のように,増分値は省略すると 1 となります.

break 文

while や for などのループから脱出したくなったときは break 文を使います.すなわち,次の 2 つの while ループは等価です:

while not n do
  print ('入力したものは数ではありません.やり直し: ')
  n = tonumber(io.read())
end

while true do
  if n then -- n が nil でも false でもないとき
    break
  end
  print ('入力したものは数ではありません.やり直し: ')
  n = tonumber(io.read())
end

変数のスコープと関数

関数定義

他の言語と同じように,プログラム内部で関数を定義することができます.関数定義は

function name (a, b)
  ...
end

のように行います.ちなみに,Lua の関数は本質的には全部無名関数であるので,上は name = function (a,b) ... end と書いても同じ事です.

関数実行中に return 文があると,関数実行はそこで終了し,結果として return 文に指定された値を返します.
  • 実行中に return 文に一度も出会わなかった場合は, nil を返します.
  • return 文は複数の値を返すこともできるし,ただ return と書くことにより何の値を返さないようにもできます.
function a(n)
  if n==1 then
    return true
  elseif n>=0 then
    return false, n
  end
end

b, c = a(1)
print(b, c) --> true nil
  -- この場合 a(1) は 1 つしか値を返していないので,c は nilとなる
b, c = a(2)
print(b, c) --> false 2
b, c = a(-1)
print(b, c) --> nil nil

例えば「不正な引数のときは nil と共に,なぜ不正なのかを表す文字列(例えば 'the argument must be positive' など)を返す」という場合に活用出来ます.

変数のスコープ

ここでの説明では,次のプログラム test2.lua を使います.

function f(c)
  local b
  a, b, c = c+1, c+1, c+1
  print('f', a, b, c)
end
c = 2
print('X', a, b, c)
print(f(c))
print('Y', a, b, c)

Lua では何も指定がなければ変数はプログラム全体で有効なグローバル変数となります.上プログラム test2.lua の後半部では,a, b, c は全てグローバル変数です.
2 行目で local 文を使って宣言されたものがローカル変数です.2行目でローカル変数として定義された b は関数 f の中でしか有効になりません
プログラム test2.lua を実行することで,両者の違いを見ていきましょう.実行させると,結果は次のようになります:

X  nil  nil  2
f  3  3  3
Y  3  nil  2
  • 2 列目はグローバル変数 a の値の変化を示しています.まず,ソース 8 行目以前には a には何も代入されていないので,nil であるとみなされます.次に,9 行目に呼ばれた関数 f の内部で,a は 3 へと変化します.この代入はグローバルのものなので,4, 10 行目の時点では,a の値は 3 に変わったままです.
  • 3 列目の変数 b についても見てみましょう.8 行目以前にはグローバル変数 b には何も代入されていないので,やはり nil であるとみなされます.
    次に関数 f の処理に移りますが,2 行目の local b によって,b はローカル変数となります.ここから先,関数内に出てきた b は今まで扱ってきたグローバル変数とは全く別物になるわけです.というわけで,3 行目の b への 3 の代入は関数の外では効力を持たず,関数 f から抜けた後の 10 行目では,グローバル変数 b の値は最初の nil のままです.
  • 関数の引数は,ローカル変数として扱われます.つまり,関数 f の中の c と,7 行目以降のグローバル変数との c とは別物です.なので,3 行目の c への 3 の代入は,関数 f の中でしか有効になりません.
Lua では,ローカル変数はグローバル変数よりもアクセスが速いので,既に前節に述べたように,片っ端からローカル変数に代入することが多いです.例えば,関数の内部でグローバル変数 hoge を(複数回)参照したい場合,関数定義の最初に

function foo(bar) 
  local hoge = hoge
    -- グローバル変数 hoge を同名のローカル変数 hoge にコピー.
    -- 以下,関数内部ではローカル変数の hoge が参照される
  ... 
end

と書いておくことが多いです.

クロージャ

他の言語と同じように,Lua でもクロージャを使うことができます.それが次の例です:

function newcnt()
  local count = 0
  return function () 
    count = count + 1
    return count
  end
end

c1, c2 = newcnt(), newcnt()
print('c1: ', c1())
print('c2: ', c2())
print('c1: ', c1())

ここで,関数 newcnt() は,値として3--6 行目に書かれた無名関数を返します.

  • newcnt() 内で定義されたローカル変数 count は,その無名関数の中で自由に使うことができます.
  • このローカル変数 count は, newcnt() の実行によって新しく作られます.
    つまり,c1 の中身の無名関数の中で使われている count と,c2 の中身で使われている count とは別のものです.
  • 従って,上のプログラムの実行結果は次のようになります:

    c1: 1
    c2: 1
    c1: 2
上のプログラムで,2 行目の local を取ると,出力結果はどうなるか?

local 文で宣言されたローカル変数の有効範囲は,その local 文のあるブロックの末尾までです.例えば,

d=1           -- グローバル変数
for i=1,1 do
  local d=2   -- for ループの中でのみ有効
  if true then
    local d=3 -- then 節の中でのみ有効
    print(d)  -- 3 が表示される
  end
  print(d)    -- 2 が表示される
end
print(d)      -- 1 が表示される

のようになっています.また,ローカル変数の有効範囲を制御するために,do ... end ブロックを使うこともできます:

do
  local d = 2 -- 有効範囲はこの do ... end ブロック内
  e = 4       -- グローバル変数
  ...
end

実習課題

関数定義 function f (n) ... endf = function (n) ... end と,
またローカルな関数定義 local function f (n) ... end

local f
f = function  (n) ... end

とそれぞれ同じ意味であることを利用し,

function fact1(n)
  return n<=0 and 1 or n*fact1(n-1)
end

do
  local function F(n)
    return n<=0 and 1 or n*F(n-1)
  end
  fact2 = F
end

print (fact1(10), fact2(10))

fc1, fc2 = fact1, fact2
fact1 = function (n) return -1 end
fact2 = function (n) return -2 end

print (fc1(10), fc2(10))

の出力結果が

3628800  3628800
-10      3628800

となることを説明せよ.

ヒント:fc1 と fact1,fc2 と fact2 はそれぞれ実体として同じ関数を表している(実体がコピーされるのではない).さて,fact1 (= fc1) の定義の中で呼ばれている fact1 は何を意味しているのだろうか?

本ページの次は テーブル や 文字列 に進んでください.
Comments