Luaリファレンス 要注意点 ~関数~

関数の定義は2種類

  • functionを前に置く書き方(シンタックスシュガー)
    function foo (x)
        return 2*x
    end
    
  • 無名関数を変数に代入する書き方
    foo = function (x) return 2*x end
    
    と、ほぼ同じ意味。

関数の定義の2種類の微妙な違い

  • シンタックスシュガー(functionを前に置く書き方)は、
    local function foo (<params>) <body> end
          ↓
    local foo
    foo = function (<params>) <body> end
    
    と展開されると考えてよい。~
    これは事前にfooを宣言しておくことで、
    自身の再帰問題(先に宣言が完了していないと未定義となる問題)を避けるため。

    逆に、無名関数を変数に代入するやり方だと、
    local fact = function (n)
       if n == 0 then return 1
       else return n*fact(n-1)
       end
    end
    
    こうなるが、
    これは、factの定義が完了しないうちに、factを呼び出しているため、エラーとなる。

    一方、
    local function foo (<params>) <body> end
    
    とシンタックスシュガーを使った場合は、
    local fact
    fact = function (n)
        if n == 0 then return 1
        else return n*fact(n-1)
        end
    end
    
    と展開されるため、問題はない。

    よって、可能な限りシンタックスシュガーを利用するべきである。

関数はfunction型

  • 関数はfunction型という変数にすぎないので、テーブルの要素などにすることも問題ない。
    abc = { function(x) return x+1 end, 2 }
    print(abc[1](3))    --> 4 
    

名前空間に関数を入れるかのような用法

  • テーブルを名前空間替わりにし、そこに関数を追加していく
    Lib = {}
    
    function Lib.foo (x, y) return x+y end
    function Lib.goo (x, y) return x+y end
    
    といった記述も可能である。

無名関数が良く登場するシーン

  • 他のスクリプト言語と同様、sort関数などで無名関数は良く登場する
    network = {
        { name = "grauna", IP = "210.26.30.34"},
        { name = "arraial",  IP = "210.26.30.23"},
        { name = "lua",      IP = "210.26.23.12"},
        { name = "gerain",  IP = "210.26.30.20"},
    }
    table.sort( network, function (a, b) return a.name > b.name end )
    

関数の( )の省略

  • 引数が1つで、文字列もしくは、テーブルコンストラクタの場合のみ( )を省略可能。
    print "abc"   ---> print("abc")
    
    f{x=10, y=20} ---> f({x=10, y=20})
    
    type{} ---> type({})
    

呼び出す側の引数の数は、少なくとも多くともOK

  • 引数の数が多かったり、不足していると、勝手に合わせてくれる。
    (正直言って、これはあまり良くない仕様であろう。メリットもあるがデメリットも大きい)
    function f(a, b)
       return a or b
    end
    
    f(3)      -- b は nil となる。
    f(3,4)
    f(3,4,5)  -- 5 は 利用されない。
    

関数とreturn

  • C,Perl,Pythonなどと異なり, LUAではreturnを「ブロックの最後]にしか記述出来ない。
    function abc(x)
        return             ---->    エラー。ブロックの最後じゃないのにreturnしている。
    
        print(x)
    end
    
  • このため、デバッグ時等で、一時的にreturnしてしまいたい場合は、以下のようにブロック化する。
    function abc(x)
        do return end      ---->    OK。
    
        print(x)
    end
    

複数の値のreturn と ( )

  • return ステートメントで( )を使ってしまうと、値が1つしか返らないので注意!!
    function abc(x, y)
        return (p(x))   -- p(x)が元来複数の値を返す関数だったとしても、( )をつけることで最初の1つに絞られてしまう。
    end
    
  • 複数の返り値を持つ関数を、安易に( )でくくってしまっても同様に値が1つに絞られてしまうので注意
    function abc(x,y)
        return x+10, y+10     -- 2つの値を返す、関数
    end
    
    local a,b = ( abc(1,2) ) -- 安易にカッコでくくると値が最初の1だけになる。a=11, b=nil となってしまう。 
    
    print( abc(1,2) ) --> 11, 12
    print( ( abc(1,2) ) ) --> 11
    

複数の値をリストのように返す

  • 複数の値をリストのように返すことが可能。
    このあたりは、Perl,Pythonと同じ。特にPythonとほぼ同じ。
      function foo() return "a", "b" end
     x,y,z = 10, foo()  --> 10, "a", "b"
    

何も返していない関数はnilを返していることと同じ

function foo() end
x = foo()          --> nil  有効な値を返していない関数(return だけ含む)はnilを返した

複数の値を返す関数とテーブルのコンストラクタ

  • 関数をリスト内に配置した時、複数の値を返すのは「リストの最後にある場合のみ」
    わかりにくい話なので、具体例で示す。
    なぜこのような仕様にしたのか理解に苦しむ、間違いやすい特性だと言える。
    function foo()
    end
    
    function foo1()
        return "a"
    end
    
    function foo2()
        return "a", "b"
    end
    
    という、3つの関数があるとする。

    この時、
    t = { foo0(), foo2(), 4 }
    ---> t = {nil, "a", "b", 4} と思うかもしれないが実際にはそうではない、
    --   foo2()は「リストの最後」に位置しないので、値はひとつだけ返すので
    --   t = {nil, "a", 4 } となる。
    

    一方、
    t = { foo0(), 4, foo2() }
    ---> t = {nil, 4, "a", "b" } はfoo2はリストの最後に位置するので、そのまま複数の値が返される。
    

複数の値を返す関数を、呼び出す際に1つだけの値としたい場合、( )で囲む

  • 関数を呼び出したもの全体をカッコで囲うことで、リストは先頭の値1つだけとなる。
    function foo2
        return "a", "b", "c"
    end
    
    print( (foo2()) )   ---> "a"
    

unpack と 関数の引数

  • unpackを使うことにより、配列をリスト化することが可能となる。これはそのまま、関数の引数としても使える。 (Pythonの*[1,2] や*(1,2)などの展開指定と同じ)
    function f(a, b)
        return a + b
    end
    
    arr = { 1, 2 }
    
    f(unpack(arr))  --> f( 1, 2 )
    

「...」と可変長引数

  • ...は可変長の「リスト」とみなされる。よって{...}などとすると、このリストを元に配列を作れる。
    function add (...)
        local s = 0
        for i, v in ipairs{...} do   -- ipairs({...})なので( )は省略可能
            s = s + v
        end
        return s
    end
    
    print(add(3,4,10,25,12))    --> 54
    
  • 可変数に利用可能という側面だけではなく、
    「複数個所から関数を呼び出している際、そのいずれかに問題があるのではないかとトレースする際に、
    呼び出し側が渡した全ての変数を把握するのに役立つ」
    function foo_check (...)
        print("calling foo:", ...)
        return foo(...)
    end
    
  • その他、C言語のprintfや各種言語のsprintf系など、
    可変長引数の関数をラッピングするのにも役立つ。
    function fwrite (fmt, ...)
       return io.write(string.format(fmt, ...))
    end
    

「...」とPerl的な値の渡し方

  • このようなことをする必要はないが理解の一環として
    function foo (...) 
        local a,b,c = ...
    end
    

「...」の一部を取り出す

  • select(n, ...) で n番目以降の引数をリストとして返す。
  • select('#', ...) で全ての総数を返す
    for i=1; select('#', ... ) do
        local arg = select(i, ...)   -- i番目のパラメタを取得
         <ループのボディ>
    end
    

名前付き引数

  • (概ねPerlのhashを使った方法と同じ考え方)
    f { old="temp.lua", new="temp1.lua" }  --> f({ old="temp.lua", new="temp1.lua" })  -- ( )内にあるテーブルコンストラクタは( )の省略が可能なのだ
    
    function f (arg)
        --以下のような方法でアクセスが可能である。
        local a = arg.old
        local b = arg.new
    end
    

末尾呼び出しとスタック

  • Luaでは、「return f(x)」のような形でreturn すると
    末尾呼び出し(スタック)排除が成立するため
    スタック積みすぎによるオーバーフローが起きない。
    (スタック積を伴う、call命令ではなく、jmp命令に変換されるため)
    function abc(x)
        if (ほげほげ)
            return abc(x)
        end
    end
    

トップ   差分 履歴 リロード   一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2016-01-23 (土) 00:00:00