#当たり前っちゃあ当たり前なのだが
なのだが! とりあえず、Javascriptについて書かれていて、CoffeeScriptだと探すのにちょっと苦労するTIPSは、遠慮なくポストします。
とりあえず、無名関数の再帰呼び出しに関してのよくある悩み事はこんな感じかな。
- arguments.calleeだったっけ? 存在がちょっとヤバゲだし、なくなるんじゃなかったっけ?
- Yコンビネータとかなにそれおいしーの? なんか怖いんだけど。
- 1引数関数の例があちこちにあるけど、2引数渡したいんだけど駄目?
まあ、3番目のは、「引数をオブジェクトに纏めてどれもこれも同じYコンビネータ使うのも良いかもね」とゆー考えもなくもないのですが、関数を引数に取る関数に関数を引数に取る関数自身を渡す関数を使う、っていうコンセプトなんだから、複数の引数だってできるに決まってんじゃん、とか、やっている人は思うんだけど、多分怖い人は怖い。それにね、1変数関数を再帰にする例って、Yコンビネータの原理が分かりにくいと思うんだよね。Yコンビネータの仕組みをカラダで感じないというか。でもな、おじさんにぜんぶまかせてごらん、きもちーよ?
でもって、augumentsという変な変数とapplyという変な関数呼び出しの仕方をすれば、いくつでも変数が渡せます。それは、きもいと感じる人にはきもいので、また別項改めて。
#では、サンプルです
折角、CoffeeScriptを使うと、Yコンビネータの見た目もちょっと優しく愛らしい感じになるので、サンプル行っときましょう。題材は、身近な再帰ということで、しかも、引数が色々増えそうな感じなのはディレクトリ走査ですかね。
fs = require('fs')
scandir_inside = (func) ->
return (srcpath, param) ->
if not fs.existsSync srcpath
return -1
filearray = fs.readdirSync srcpath
for fileordir in filearray
statresult = fs.statSync srcpath+'/'+fileordir
if statresult.isFile()
console.log 'scanfiles '+fileordir
else if statresult.isDirectory()
console.log 'scandirs '+fileordir
func srcpath+'/'+fileordir,param
#所謂Yコンビネータ
y_with_2_args = (func) ->
return ((p) ->
return (q,r) ->
return func(p(p))(q,r)
)((p) ->
return (q,r) ->
return func(p(p))(q,r)
)
#Yコンビネータを噛ます
scandir = y_with_2_args(scandir_inside)
#ためしてみようほととぎす
scandir '/path/to/start/directory',{project: 'testtest', test:12}
scandir_insideの最後の行で、funcを呼んでいますね。これは、1行目の引数のfuncです。つまり、渡される関数を呼び出すクロージャになっているだけで、これだけでは必ずしも再帰とは限らない書き方なわけですが、まあ、再帰しようと思って書いてますよね。どうみても。
で、Yコンビネータが、「引数(である関数)を、引数(である関数)を引数として呼び出す」ことによって、再帰を実現します。入口と出口があるパイプ(scandir_inside)に入口と出口を繋げる装置(y_with_2_args)を嵌めこんだら、循環する機械ができました(scandir)って感じ。
ここでのYコンビネータ(別名不動点コンビネータ)は、2引数用。3引数にしたければ、4箇所全部q,r,sにして下さい。「引数(である関数)を、引数(である関数)を引数として呼び出す」ので、その「引数(である関数)」の呼び出しの形は合わせないと使えない、ってことです。
さて、追記しておきましょう。さっきのは原理を理解しやすくするためにscandir_insideなんて変数に入れたけど、もう分かりますよね。直接Yコンビネータの引数の中に無名関数を入れちゃえば、そんなの要らなくなると言うことを!
なので、実践的にはこんな風に書くとカコイイ!です。
fs = require('fs')
#所謂Yコンビネータ
y_with_2_args = (func) ->
return ((p) ->
return (q,r) ->
return func(p(p))(q,r)
)((p) ->
return (q,r) ->
return func(p(p))(q,r)
)
#Yコンビネータを噛ましつつ無名関数を書いちゃう
scandir = y_with_2_args (func) ->
return (srcpath, param) ->
if not fs.existsSync srcpath
return -1
filearray = fs.readdirSync srcpath
for fileordir in filearray
statresult = fs.statSync srcpath+'/'+fileordir
if statresult.isFile()
console.log 'scanfiles '+fileordir
else if statresult.isDirectory()
console.log 'scandirs '+fileordir
func srcpath+'/'+fileordir,param
#ためしてみようほととぎす
scandir '/path/to/start/directory',{project: 'testtest', test:12}
使えばだんだん気持ちよくなるけど、別に奥は深くないTIPSです。ご参考まで。