イントロ
ある関数g
を引数として他の関数f
に渡すというのは非常に便利な機能である。特に無名関数を渡すような使い方は頻繁に現れる。以下はmap
関数の例である(fがmap, gがx->x^2)。
map(x->x^2, [1,2,3])
ただし、無名関数の部分が非常に長くなるとかなり煩雑で読み書きし難いコードになる。公式ドキュメントでは条件分岐を含むような関数をmap
に渡す例が載っている([A,B,C]
は何か適当なものを指してるだけ):
map(
x->begin
if x < 0 && iseven(x)
return 0
elseif x == 0
return 1
else
return x
end
end,
[A,B,C]
)
こういう関数を引数にとる関数を扱うための便利な機能としてdoブロックがある。それについてのメモ。
記法
まず、どういう記法かを書いてしまう。以下のような記法である。関数f
は第一引数に関数を受け取れるものとする。このとき、
f(第一引数以外) do 無名関数の引数
無名関数が行う処理
end
とかくと、doの次の部分を引数にとり、その後のブロック内の処理を行う無名関数を作って、fに渡すというコードになる。たとえば、さきほどのmap
の例なら以下のようになる。
map([1,2,3]) do x
x^2
end
map([A,B,C]) do x
if x < 0 && iseven(x)
return 0
elseif x == 0
return 1
else
return x
end
end
do a, b
とすれば2個の引数a
とb
を受け取る無名関数が作れる。3個以上も同様。ただし、(a,b)
と書くと、1つのタプルを受け取る関数になるので注意。
用法
map関数以外にもたくさんの用法がある。いくつかの便利な用例を書いておく。
安全なファイル処理
たとえば、ファイルを開いて何かを処理したあと必ずそれが閉じられるようにしておきたい場合があるだろう(毎度、直接open()としてあとでclose()を書くのではなくて。closeを書き忘れたりしそうじゃない?)。ここで、処理については任意の関数を想定しておきたい。つまり
io = open(...)
try
ioに対する何らかの処理
finally
close(io)
end
というようなことをしたいのだが、tryの中身は色々あり得る。毎回これらを書くのは面倒なので何とかしたいというわけである。このような場合には、処理部分を関数として受け取り、一連の処理を行う関数を作ればよいが、まさにdoブロックが活躍する場面になっている。
function open(f::Function, args...)
io = open(args...)
try
f(io)
finally
close(io)
end
end
として、
open("outfile", "w") do io
ioに対する何らかの処理
end
とすればよい。ファイルの処理の部分はopen内に完全に隠されて、単にファイルに対して何をするかを普通のコードのように書けばよい。
Fluxの勾配計算
他の例として、Fluxという機械学習向けのJuliaのpackageにおける勾配計算の記法がある。モデルm
があり、それを使って関数l
(損失関数とかを想定すればよい)を計算したうえで、Flux.params(m)
に対して微分したいとする。このような場合、素朴にはFlux.gradient(()->l(hogehoge), Flux.params(m))
とかなのだがl
の部分は複雑になりうる。ここでl
の部分をdoブロックを使って外に出しておけばどこまでが関数引数なのかわからなくなったりしない:
gradient(Flux.params(m)) do
l(hogehoge)
end
なお、gradient
は「引数をとらない関数」を引数にとるので、doの後ろの引数の部分は空白になっている
参考
- 公式document
- [Julia その5 関数関連の続き](https://qiita.com/hidemotoNakada/items/3d08770def0a2b40f022