proc Luaの世界④Luaの関数は柔軟で奥が深いな~って話

Fukiyaさんの新作記事「Proc Luaを勉強してみての感想とProc Luaをオブジェクト指向プログラミグの試み」が激アツいです。

今日は関数のお勉強になるんですが、これがまた深いところなんで、いつも言ってますが
とりあえず簡単なとこだけやります。
僕もまだまだよくわかってなくて、本当はFukiyaさんかmatsuさんに解説してもらってから記事書きたいぐらいです。間違ったこと言ってたら殴ってくださいね

さて

と、そのまえに前回記事の補足です

sas.submit([[SASコード]],{置換パラメータ指定})
置換パラメータ指定の部分ですが、別にsubmitの中で指定しなくても
実行時点で置換パラメータがLuaの中で変数として定義されていればそれが適応されます

ので以下のコードも成立します。

proc lua;
 submit;
 local ds="b"
 local var="y"
 local code=[[data @ds@;
              @var@=1;
             run;]]
 sas.submit(code)
endsubmit;
quit;

あと、さらに唐突な補足です。Luaの論理式はFalseとnilが偽でそれ以外が真という話をしました。それに絡んで面白い性質があって、以下のような代入式(SASでいう割り当てステートメント)において左辺をorで結ぶと、左から順に評価して、偽である値はスキップされ、初めて真である値が代入されます
コードと結果を見てください


proc lua;
 submit;
local a = 99;
local b = nil;

local v1 = b or a
local v2 = (1>2) or a
local v3 = d or e or f or a or 88
local v4 = g or h

print(v1)
print(v2)
print(v3)
print(v4)

 endsubmit;
quit;

結果






それがなんじゃい?なにに使うんじゃい?と思われた方はちょっと甘い!
 X=Y or 99 は、YがあればYの値を使うし、Yがなければ99を使う。初期化されていない変数にはnilが入っている。それらの性質を使うと、パラメータのデフォルト値を設定できるということなんですよ。
SASマクロにも、キーワードパラメータが指定されなかったときにはこの値を使うみたいな書き方できるでしょう、あれと同じことができるんですね

はい、いよいよ本題です。

Luaの関数ですが、以下のように作って、使います

proc lua;
 submit;
--定義
function add(a,b)
  return a+b
end

--型もみてみよう
print(type(add))

--早速使ってみる
print(add(1,2))
print(add(1,5))
 endsubmit;
quit;

結果






文法は
function 関数名(引数)
戻り値があるならreturn 戻り値
end

です。簡単ですね。

ただ、細かい話なんですが、正確に言うと、Luaには「関数名」っていう考え方は間違ってるんです。
関数名というものは存在しません。

はぁ?って話なんですが、以下のコードを見てください

proc lua;
 submit;
 --[[厳密にいうとLuaの関数に名前はない(無名関数)、
   関数を変数に入れて使っており、変数に名前がついている
]]--
add=function(a,b)
  return a+b
end
 endsubmit;
quit;

実は最初に紹介したのは、こういう書き方もできるって話で糖衣構文みたいなもんです。
本質的には

変数名 = function(引数)
戻り値があるならreturn 戻り値
 end

なんです。これは無名関数といって、本当は関数自体には名前がなくて、その名前のない処理を
変数に入れる。変数には名前がある。ので関数の入ってる変数名がいわゆる関数名みたいになるということです。

だからそれがどうしたって話なんですけど、一応知っておいてください。
いずれ複雑なことをするときに必要になるので。

さて、わけのわからん話はやめて、続きです。
冒頭に紹介したorを使えば、関数の引数を省略した場合の挙動を制御できますよという話

proc lua;
 submit;
 function add(a,b)
  local a= a or 3
  local b= b or 5
    return a+b
 end
 print(add(aaa))
 print(add(1))
 print(add(nill,1))
 endsubmit;
quit;

結果






そして、引数の部分について以下のようにハッシュ型の変数定義も使うことができます
こうすると本当キーワードパラメータマクロのようですね

proc lua;
 submit;
function add(para)
para.a= para.a or 3
para.b= para.b or 5
  return para.a+para.b
end
print(add({a=2,b=2}))
print(add({a=2}))
print(add({b=2}))
 endsubmit;
quit;

結果






戻り値のない関数も全然ありなので
たとえば、以下のようなSASコードの実行を関数にすることもできますよっと

proc lua;
 submit;
function ds_make(para)
local ds= para.ds or "aaa"
local var= para.var or "bbb"
sas.submit([[data @ds@;@var@=1;run;]])
end

ds_make({ds="ds1",var="var1"})
ds_make({})
 endsubmit;
quit;



次に、引数の数がが可変の場合の定義です。
以下のように...とかきます。その後、関数の中でテーブルに入れて
捌いてやればOK。
引数がいくつでも動く関数ができます

proc lua;
 submit;
function add3(...)
  list = {...}
  result=0
  for val in pairs(list) do
     result =result+val
  end
  return (result)
end
print(add3(1,2))
print(add3(1,2,3,4))
 endsubmit;
quit;

結果




・・・で引数を定義するのが面白いですね、受け取ったらまずテーブルに入れて捌いてます。
ループで足してます。sum関数みたいなのができましたね。


逆に、戻り値が複数あるパターン。
海外論文にも載ってた(論文のコードには誤植あるけど)、本日日付を年月日に分解して
3つの戻り値をえるコードです

proc lua;
 submit;
function split_date()
local date = sas.date()
return sas.day(date), sas.month(date), sas.year(date)
end

local day, month, year = split_date()
print("d=", day, "m=", month, "y=",year)

 endsubmit;
quit;

結果(実行したのは2016/8/30です)





さらに、戻り値をテーブルにしたり、テーブルを引数にするパターンを見てみましょう。
まず、与えた文字列を1文字ずつ分解してテーブルを返すsplit_char関数をつくってみて

その後、テーブルを受け取ってまた1つの文字にするcats_table関数を作ってみました。

ただテーブルから文字をつくるのは普通にtable.concat関数というものがあって、これだと連結文字や対象範囲も指定できて、絶対こっちの方がいいです。車輪の再発明も勉強にはなりますけどね

proc lua restart;
 submit;
    --文字列を1文字ずつ取り出してテーブルにする関数(戻り値はテーブル)
function split_char(char)
local result_table={}
local len=string.len(char)
for i =1,len do
result_table[i]=string.sub(char, i,i)
     end
return(result_table)
end

local tb1=split_char("abc")
print("★文字列が分解されてテーブルになりました→",table.tostring(tb1))

--テーブルを引数にして、テーブルの中身を連結して1つの文字値にする関数
function cats_table(table)
local result=''
for i, item in pairs(table) do
  result = result..item
    end
return result
end

local st1=cats_table(tb1)
print("★テーブルが連結されて文字列にになりました→",st1)

local st2=table.concat(tb1,"/")
    print("★concat関数はcatxみたいで便利だな→",st2)


 endsubmit;
quit;

結果

なんかFCMPよりだいぶ書きやすいなぁって思うのは僕だけしょうか。


--おまけ

ちょっと難しい話になりますが、
関数は、変数に過ぎないので、関数の中に関数を入れることができ
それを使うと関数を作る関数とかも定義できます

proc lua;
submit;

function concat3_func(x,y,z)
   print(x..' '..y..' '..z)
end

function make_func(x,y)
   return function (target)
    concat3_func(x, y, target)
  end
end

i_love_func =make_func("I","love")
you_love_func =make_func("You","love")
i_study_func =make_func("I","study")

print(i_love_func("SAS"))
print(you_love_func("SAS"))
print(i_study_func("Lua"))

endsubmit;
quit;









こういうのの詳しい話はまたいずれ。とりあえず今日はここまで!!




7 件のコメント:

  1. luaの紹介ありがとうございます。これからですが・・・
    ところで「SASプログラミング掲示板」が見れなくなっています。
    回復を願っています!

    返信削除
  2. どうもです!まだまだこれからですね。
    掲示板、なんか調子悪いんですよね、利用してるサービス元からよくメンテナンスがどうとかの連絡がきてるんですが、、そのうち安定すると思います。

    返信削除
  3. 「SASプログラミング掲示板」が混乱中なので残念です。
    そこでproc luaについて、ここで質問させてください。

    SASYAMA/amatsuさんのホームページで紹介のdosubl関数を用いて、例えば次のように書いて、Sashelp.Classの6番目のSexと同じ性別のものだけを、Sashelp.Classから抽出することができるマクロ:%InfValは作成可能です。

    luaプロシジャを用いて、同じことが可能なのかと考えています。
    今のところ目的が違うので、これは無理だと思っていますが、可能でしょうか?


    data A1;
    set Sashelp.Class(where=(Sex="%InfVal( Ds=Sashelp.Class, Var=Sex, Row=6)"));
    run;

    返信削除
    返信
    1. 質問ありがとうございます。
      可能ですね。基本的にマクロでできることはproc luaでも全て可能だと思います。

      ざっと簡単に書きましたが、以下の様な感じでしょうか。
      InfValマクロと同機能のInfVal関数を作り、パラメータを指定して実行し、戻り値をSASコードに埋め込んで実行しています。
      関数部分はもう少し綺麗に書けるかもしれません


      proc lua;
      submit;
      --関数InfVal定義部分
      local function InfVal(ds,var,rowno)
      local dsid = sas.open(ds)
      local ct=0
      local wh
      while sas.next(dsid) do
      ct=ct+1
      if ct==rowno then wh=(sas.get_value(dsid,var)) end
      end
      sas.close(dsid)
      return wh
      end

      --実行部分
      wh=InfVal( "sashelp.class","sex",6)
      sas.submit([[data A1;
      set sashelp.class;
      where sex="@wh@";
      run;]])

      endsubmit;
      quit;

      削除
  4. SASYAMAさん

    すぐにResponse戴いたのに、確認遅れて申し訳ありません。
    luaって、何でもできそうですね。

    申し訳ありませんが、同じような質問です。

    下記プログラムを書くことで、あるデータセット/変数の値が、任意のデータセット/行/変数の値と同じものを抽出したい場合に、proc luaを使ったマクロmmmを作成することは可能でしょうか。

    data A1:
    set Sashelp.Class(where=(Sex="%mmm(Ds,Var,Row)"));
    run;

    実は、dosubl関数の使い方が、少しだけ私にはなじまないので(例を見ないでは書けない)、他の方法を模索している状況です。

    よろしくお願いします。



    返信削除
    返信
    1. 返信有難うございます。

      多分、先の返信で書いたluaのInfVal関数が、まさに同じ機能になっているはずです。
      マクロのように何度も使うのであればグローバルで定義してやればよいと思います。
      そうすれば実行するときにluaで以下のようにsubmitするだけでよいので。
      wh=InfVal( "sashelp.class","sex",6)
      sas.submit([[
      data XXX;
      set YYYY;
      where ZZ="@wh@";
      run;
      ]])

      proc lua自体をマクロ化して、データステップ中に埋め込むという意図であればそれは無理です。データステップ中にproc stepを動かすとなると、それこそdosublを使う必要がでてきますが本末転倒になってしまうので。

      luaをマクロの中に入れるのはちょっとお勧めできない、というか意味がないかもしれないです。それなら全部マクロで書くか、luaで書くかで統一した方がよいのではと思います。
      有効なケースもあるかもしれませんが。


      削除
  5. 回答ありがとうございました。
    別の視点からluaの可能性を考えてみます。

    返信削除